Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: restore part #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,581 changes: 1,578 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.7.1",
"description": "A JavaScript implementation of Shamir's Secret Sharing algorithm over GF(256).",
"main": "src/main/js/Scheme.js",
"typings": "./shamir.d.ts",
"scripts": {
"test": "tape src/test/js/GF256Tests.js src/test/js/SchemeTests.js src/test/js/TieredSharing.js",
"testwithjava": "node --jvm --vm.cp=./target/classes:./target/test-classes src/test/js/PolygotTests.js",
Expand Down
6 changes: 6 additions & 0 deletions shamir.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type RandomBytes = (length: number) => Uint8Array;
export type Parts = Record<string, Uint8Array>;

export function split(randomBytes: RandomBytes, n: number, k: number, secret: Uint8Array): Parts;
export function join(parts: Parts): Uint8Array;
export function restorePart(parts: Parts, partIdx: number): Uint8Array;
8 changes: 6 additions & 2 deletions src/main/java/com/codahale/shamir/GF256.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,12 @@ static byte[] generate(SecureRandom random, int degree, byte x) {

static byte interpolate(byte[][] points) {
// calculate f(0) of the given points using Lagrangian interpolation
final byte x = 0;
byte y = 0;
return interpolate(points, (byte)0);
}

static byte interpolate(byte[][] points, byte partIdx) {
// calculate f(partIdx) of the given points using Lagrangian interpolation
byte x = partIdx, y = 0;
for (int i = 0; i < points.length; i++) {
final byte aX = points[i][0];
final byte aY = points[i][1];
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/codahale/shamir/Scheme.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,37 @@ public byte[] join(Map<Integer, byte[]> parts) {
return secret;
}

/**
* Restores a part given the original parts and a new index.
*
* <p><b>N.B.:</b> There is no way to determine whether or not the returned value is actually a
* valid part. If the parts are incorrect, or are under the threshold value used to split the
* secret, a random value will be returned.
*
* @param parts a map of part IDs to part values
* @param partIdx the index for the part to restore
* @return the restored part
* @throws IllegalArgumentException if {@code parts} is less than 2 or contains values of varying
* lengths
*/
public byte[] restorePart(Map<Integer, byte[]> parts, int partIdx) {
checkArgument(parts.size() > 1, "Need at least two parts");
final int[] lengths = parts.values().stream().mapToInt(v -> v.length).distinct().toArray();
checkArgument(lengths.length == 1, "Parts have varying lengths");
final byte[] restoredPart = new byte[lengths[0]];
for (int i = 0; i < restoredPart.length; i++) {
final byte[][] points = new byte[parts.size()][2];
int j = 0;
for (Map.Entry<Integer, byte[]> part : parts.entrySet()) {
points[j][0] = part.getKey().byteValue();
points[j][1] = part.getValue()[i];
j++;
}
restoredPart[i] = GF256.interpolate(points, (byte)partIdx);
}
return restoredPart;
}

/**
* The number of parts the scheme will generate when splitting a secret.
*
Expand Down
7 changes: 4 additions & 3 deletions src/main/js/GF256.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,12 @@ function degree(p) {
exports.degree = degree;

/**
* Calculates f(0) of the given points using Lagrangian interpolation.
* Calculates f(partIdx) of the given points using Lagrangian interpolation.
* @param {array[Uint8Array]} points The supplied point.
* @param {Number} partIdx The index to interpolate at.
*/
function interpolate(points) {
const x = 0;
function interpolate(points, partIdx = 0) {
const x = partIdx;
let y = 0;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < points.length; i++) {
Expand Down
36 changes: 36 additions & 0 deletions src/main/js/Scheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,39 @@ function join(parts) {
}

exports.join = join;


/**
* Restores a part given a map of parts and a new index.
*
* @param {Object.<string, Uint8Array>} parts a map of part IDs to part values
* @param {number} partIdx the new index for the part
* @return {Uint8Array} the restored part
* @throws {Error} if parts is empty or contains values of varying lengths
*/
function restorePart(parts, partIdx) {
if (Object.keys(parts).length <= 1) throw new Error('Need at least two parts');
const lengths = Object.values(parts).map(x => x.length);
const max = Math.max.apply(null, lengths);
const min = Math.min.apply(null, lengths);
if (max !== min) {
throw new Error(`Parts have varying lengths. Min ${min}, Max ${max}`);
}
const restoredPart = new Uint8Array(max);
for (let i = 0; i < restoredPart.length; i++) {
const keys = Object.keys(parts);
const points = new Array(keys.length)
.fill(0)
.map(() => new Uint8Array(2).fill(0));
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
const k = Number(key);
points[j][0] = k;
points[j][1] = parts[key][i];
}
restoredPart[i] = GF256.interpolate(points, partIdx);
}
return restoredPart;
}

exports.restorePart = restorePart;
31 changes: 31 additions & 0 deletions src/test/java/com/codahale/shamir/tests/SchemeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.jupiter.api.Test;
import org.quicktheories.WithQuickTheories;

Expand Down Expand Up @@ -126,6 +128,35 @@ void splitAndJoinInquorate() {
});
}

@Test
void restorePartRoundtrip() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, nice! how do you execute these tests?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mvn test
but i don't have maven localy installed
maven is gradle's predecessor

qt().forAll(integers().between(2, 5), integers().between(1, 5), byteArrays(1, 300))
.asWithPrecursor((k, extra, secret) -> new Scheme(new SecureRandom(), k + extra, k))
.checkAssert((k, e, secret, scheme) -> {
final Map<Integer, byte[]> splits = scheme.split(secret);
final Map<Integer, byte[]> partsToRestoreFrom = new HashMap<>();
final List<Integer> indexes = IntStream.rangeClosed(1, k + e).boxed().collect(Collectors.toList());
Collections.shuffle(indexes);
for (int i = 0; i < k; i++) {
partsToRestoreFrom.put(indexes.get(i), splits.get(indexes.get(i)));
}

// pick a random index for the restored part
final int restoredPartIdx = indexes.get(k);

final byte[] restoredPart = scheme.restorePart(partsToRestoreFrom, restoredPartIdx);
final Map<Integer, byte[]> splitsWithRestored = new HashMap<>();
splitsWithRestored.put(restoredPartIdx, restoredPart);
for (int i = 0; i < k; i++) {
if (i != restoredPartIdx) {
splitsWithRestored.put(indexes.get(i), splits.get(indexes.get(i)));
}
}
final byte[] joined = scheme.join(splitsWithRestored);
assertThat(joined).isEqualTo(secret);
});
}

private byte[] join(Scheme scheme, Set<Map.Entry<Integer, byte[]>> entries) {
final Map<Integer, byte[]> m = new HashMap<>();
entries.forEach(v -> m.put(v.getKey(), v.getValue()));
Expand Down
18 changes: 17 additions & 1 deletion src/test/js/SchemeTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

const test = require('tape');

const { split, join } = require('../../main/js/Scheme.js');
const { split, join, restorePart } = require('../../main/js/Scheme.js');

const { randomBytes } = require('crypto');

Expand Down Expand Up @@ -89,6 +89,22 @@ test('SchemeTests roundtrip two parts', function (t) {
t.end();
});

test('restorePart roundtrip', function(t) {
const parts = 5;
const quorum = 2;
const secretUtf8 = `ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ
ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ
ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬`;
const secret = stringToBytes(secretUtf8);
const splits = split(randomBytes, parts, quorum, secret);
const partsToRestoreFrom = { '1': splits['1'], '2': splits['2'] };
const restoredPart = restorePart(partsToRestoreFrom, 3);
const splitsWithRestored = {'1': splits['1'], '3': restoredPart};
const joined = bytesToSring(join(splitsWithRestored));
t.deepEqual(secretUtf8, joined);
t.end();
});

test('SchemeTests split input validation', function (t) {
const secretUtf8 = 'ᚠᛇᚻ';
const secret = stringToBytes(secretUtf8);
Expand Down