Skip to content

Commit 3ecf7b9

Browse files
committed
Sucessfully created shares compatable with secrets.js-grempe
1 parent 602ef98 commit 3ecf7b9

5 files changed

Lines changed: 278 additions & 12 deletions

File tree

gems/share.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#! /usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
# vim: set et sw=4 fenc=utf-8:
4+
#
5+
# splitNumStringToIntArray.py.py
6+
7+
import random
8+
import js2pysecrets as secrets
9+
from js2pysecrets.settings import Settings
10+
settings = Settings()
11+
12+
13+
14+
random_list=[]
15+
def dithering(random_string):
16+
#random_list.append('hello')
17+
random_list.append(hex(int(random_string)))
18+
19+
#print(settings.dithering)
20+
21+
#test = lambda bits: bin(1+random.getrandbits(bits))[2:].zfill(bits)
22+
23+
#print(test(2345))
24+
25+
secrets.init(4)
26+
#secrets.init()
27+
#print(max(settings.logs[1:]))
28+
29+
#settings.update_defaults(dithering=lambda string: dithering(string))
30+
31+
results = secrets.share('0074007300650074002000610020007300690073006900680074', 6, 3)
32+
33+
print(results)
34+
35+
#print(random_list)

gems/shuffle.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#! /usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
# vim: set et sw=4 fenc=utf-8:
4+
#
5+
# splitNumStringToIntArray.py.py
6+
7+
import js2pysecrets as secrets
8+
import random
9+
10+
data = ['remove me', 'bb', 'cc', 'dd', 'ee', 'ff', 'gg', 'hh', 'ii', 'jj']
11+
print(data)
12+
data.pop(0)
13+
random.shuffle(data)
14+
print(data)
15+
print(data[-3:])
16+
17+
18+
def pieces(shares, number=3):
19+
shares.pop(0) # Remove first share
20+
random.shuffle(shares) # Randomize
21+
return shares[-number:]
22+
23+
data = ['remove me', 'bb', 'cc', 'dd', 'ee', 'ff', 'gg', 'hh', 'ii', 'jj']
24+
25+
for i in range(10):
26+
data = ['remove me', 'bb', 'cc', 'dd', 'ee', 'ff', 'gg', 'hh', 'ii', 'jj']
27+
print(pieces(data))

js2pysecrets/base.py

Lines changed: 159 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def init(bits=None, rngType=None):
122122
# Construct the exp and log tables for multiplication.
123123
primitive = settings.primitive_polynomials[settings.bits]
124124

125-
temp_logs = {} # Temporary Dict to hold logs
125+
temp_logs = {} # Temporary Dict to hold indexed logs
126126
for i in range(settings.size):
127127
# this works with loop below
128128
exps.insert(i, x)
@@ -229,30 +229,120 @@ def splitNumStringToIntArray(string, pad_length=None):
229229
string = string[::-1]
230230

231231
for i in range(0, len(string), settings.bits):
232-
print(string[i : i + settings.bits][::-1])
232+
# print(string[i : i + settings.bits][::-1])
233233
parts.append(int(string[i : i + settings.bits][::-1], 2))
234234

235235
return parts
236236

237237

238238
def horner(x, coeffs):
239+
if x not in settings.logs:
240+
raise ValueError(f"Value of 'x' ({x}) is not found in logs.")
241+
239242
logx = settings.logs[x]
240243
fx = 0
241244

242245
for i in range(len(coeffs) - 1, -1, -1):
243246
if fx != 0:
244-
fx = (
245-
settings.exps[(logx + settings.logs[fx]) % settings.maxShares]
246-
^ coeffs[i]
247-
)
247+
248+
try:
249+
250+
fx = (
251+
settings.exps[
252+
(logx + settings.logs[fx]) % settings.maxShares
253+
]
254+
^ coeffs[i]
255+
)
256+
257+
except IndexError:
258+
raise IndexError(
259+
f"list index out of range: x:{x} i:{i} fx:{fx}"
260+
)
261+
248262
else:
249263
fx = coeffs[i]
250264

251265
return fx
252266

253267

254-
# lambda bits: bin(1+random.getrandbits(bits))[2:].zfill(bits)
255-
# lambda bits: bin(1+secrets.randbits(bits))[2:].zfill(bits)
268+
def getShares(secret, num_shares, threshold):
269+
shares = []
270+
coeffs = [secret]
271+
272+
rng = getRNG()
273+
274+
for i in range(1, threshold):
275+
276+
# Generate non-zero random number
277+
random_number = rng(settings.bits)
278+
while int(random_number, 2) < 0:
279+
random_number = rng(settings.bits)
280+
281+
# Check if dithering function is specified and callable, this captures
282+
# the random data using the provided function for dithering or similar
283+
# auditing purposes.
284+
if settings.dithering and callable(settings.dithering):
285+
capture = settings.dithering
286+
capture(random_number)
287+
288+
coeffs.append(int(random_number, 2))
289+
290+
for i in range(1, num_shares + 1):
291+
shares.append({"x": i, "y": horner(i, coeffs)})
292+
293+
return shares
294+
295+
296+
def constructPublicShareString(bits, share_id, data):
297+
share_id = int(share_id, settings.radix)
298+
bits = bits or settings.bits
299+
bits_base36 = base36encode(bits).upper()
300+
id_max = 2**bits - 1
301+
id_padding_len = len(hex(int(id_max))[2:])
302+
id_hex = padLeft(hex(int(share_id))[2:], id_padding_len)
303+
304+
if not (
305+
isinstance(share_id, int)
306+
and share_id % 1 == 0
307+
and 1 <= share_id <= id_max
308+
):
309+
raise ValueError(
310+
f"Share id must be an integer between 1 and {id_max}, inclusive."
311+
)
312+
313+
new_share_string = bits_base36 + id_hex + data
314+
315+
return new_share_string
316+
317+
318+
def base36encode(number, alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
319+
"""Converts an integer to a base36 string."""
320+
if not isinstance(number, int):
321+
raise TypeError("number must be an integer")
322+
323+
base36 = ""
324+
sign = ""
325+
326+
if number < 0:
327+
sign = "-"
328+
number = -number
329+
330+
if 0 <= number < len(alphabet):
331+
return sign + alphabet[number]
332+
333+
while number != 0:
334+
number, i = divmod(number, len(alphabet))
335+
base36 = alphabet[i] + base36
336+
337+
return sign + base36
338+
339+
340+
def base36decode(number):
341+
return int(number, 36)
342+
343+
344+
# lambda bits: bin(random.getrandbits(bits))[2:].zfill(bits)
345+
# lambda bits: bin(secrets.randbits(bits))[2:].zfill(bits)
256346
# lambda bits: (bin(123456789)[2:].zfill(32) * -(-bits // 32))[-bits:]
257347
def setRNG(new_rng=None):
258348
defaults = settings.get_defaults()
@@ -357,6 +447,67 @@ def random(bits):
357447
return bin2hex(rng(bits))
358448

359449

450+
def share(secret, num_shares, threshold, pad_length=None):
451+
# Security: pad in multiples of 128 bits by default
452+
pad_length = pad_length or 128
453+
454+
if not isinstance(secret, str):
455+
raise ValueError("Secret must be a string.")
456+
457+
if not isinstance(num_shares, int) or num_shares < 2:
458+
raise ValueError("Number of shares must be an integer >= 2.")
459+
460+
if num_shares > settings.maxShares:
461+
needed_bits = math.ceil(math.log(num_shares + 1) / math.log(2))
462+
raise ValueError(
463+
f"Number of shares must be <= {settings.settings.maxShares}."
464+
f" Use at least {needed_bits} bits."
465+
)
466+
467+
if not isinstance(threshold, int) or threshold < 2:
468+
raise ValueError("Threshold number of shares must be an integer >= 2.")
469+
470+
if threshold > settings.maxShares:
471+
needed_bits = math.ceil(math.log(threshold + 1) / math.log(2))
472+
raise ValueError(
473+
f"Threshold number of shares must be <= "
474+
f"{settings.settings.maxShares}. Use at least {needed_bits} bits."
475+
)
476+
477+
if threshold > num_shares:
478+
raise ValueError(
479+
"Threshold number of shares must be less than or equal to the "
480+
"total shares specified."
481+
)
482+
483+
if not isinstance(pad_length, int) or pad_length < 0 or pad_length > 1024:
484+
raise ValueError(
485+
"Zero-pad length must be an integer between 0 and 1024 inclusive."
486+
)
487+
488+
secret = "1" + hex2bin(secret) # prepend a 1 as a marker
489+
490+
secret = splitNumStringToIntArray(secret, pad_length)
491+
492+
num_shares = int(num_shares)
493+
threshold = int(threshold)
494+
# bits = 128 # Assuming bits as 128, you can adjust it accordingly
495+
496+
x = [None] * num_shares
497+
y = [None] * num_shares
498+
499+
for i in range(len(secret)):
500+
sub_shares = getShares(secret[i], num_shares, threshold)
501+
for j in range(num_shares):
502+
x[j] = x[j] or str(sub_shares[j]["x"])
503+
y[j] = padLeft(bin(sub_shares[j]["y"])[2:]) + (y[j] or "")
504+
505+
for i in range(num_shares):
506+
x[i] = constructPublicShareString(settings.bits, x[i], bin2hex(y[i]))
507+
508+
return x
509+
510+
360511
# # Core Functions from secrets.js
361512
# init = jsFunction('init')
362513
# combine = jsFunction('combine')

js2pysecrets/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
@dataclass
88
class Defaults:
9-
rng = lambda self, bits: bin(1 + random.getrandbits(bits))[2:].zfill(bits)
9+
rng = lambda self, bits: bin(random.getrandbits(bits))[2:].zfill(bits)
1010
dithering = None
1111
bits: int = 8 # default number of bits
1212
radix: int = 16 # work with HEX by default

tests/test_basePrivate.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -619,9 +619,6 @@ def set_init_bits(request):
619619
# Add more test cases as needed
620620
],
621621
)
622-
623-
624-
# Define the test function
625622
def test_horner(x, coeffs, set_init_bits):
626623
secrets.init(set_init_bits)
627624
# assert secrets.horner(x, coeffs) == node._horner(x, coeffs)
@@ -632,3 +629,59 @@ def test_horner(x, coeffs, set_init_bits):
632629
js_results = chain(node_data)
633630

634631
assert secrets.horner(x, coeffs) == js_results[-1]
632+
633+
634+
def test_getShares(set_init_bits):
635+
secrets.init(set_init_bits, "testRandom")
636+
py_results = secrets.getShares(1234, 6, 3)
637+
638+
node_data = []
639+
node_data.append(node.init(set_init_bits, "testRandom", list=True))
640+
node_data.append(node._getShares(1234, 6, 3, list=True))
641+
js_results = chain(node_data)
642+
643+
for i in range(6):
644+
assert py_results[i]["x"] == js_results[-1][i]["x"]
645+
assert py_results[i]["y"] == js_results[-1][i]["y"]
646+
assert True
647+
648+
649+
@pytest.fixture(
650+
params=[
651+
None,
652+
3,
653+
4,
654+
5,
655+
6,
656+
7,
657+
8,
658+
9,
659+
10,
660+
11,
661+
12,
662+
13,
663+
14,
664+
15,
665+
16,
666+
17,
667+
18,
668+
19,
669+
20,
670+
]
671+
)
672+
def full_range_of_bits(request):
673+
return request.param
674+
675+
676+
def pieces(shares, number=3):
677+
random.shuffle(shares) # Randomize
678+
return shares[-number:]
679+
680+
681+
def test_py_share(full_range_of_bits):
682+
secrets.init(full_range_of_bits)
683+
secret = secrets.random(64)
684+
shares = secrets.share(secret, 6, 3)
685+
686+
result = node.combine(pieces(shares, 3))
687+
assert result == secret

0 commit comments

Comments
 (0)