Skip to content

Commit f571a24

Browse files
authored
fix(crypto): improve handling of a share key being passed twice (#75)
1 parent d1aa3ed commit f571a24

2 files changed

Lines changed: 58 additions & 6 deletions

File tree

packages/crypto/src/shamir.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,17 +189,36 @@ export function split(
189189
*/
190190
export function reconstruct(
191191
parts: BufferSource[],
192-
neededParts: u8 = parts.length,
192+
neededParts: u8 = undefined,
193193
): Uint8Array {
194-
if (parts.length < neededParts)
195-
throw new Error("Not enough parts to reconstruct key");
196194
const bytes = parts[0].byteLength - 1;
197195
const result = new Uint8Array(bytes);
198196
const dataViews = parts.map(part =>
199197
ArrayBuffer.isView(part)
200198
? new DataView(part.buffer, part.byteOffset, bytes + 1)
201199
: new DataView(part),
202-
);
200+
).filter((shareView, i, shares) => {
201+
const x = shareView.getUint8(bytes);
202+
for (let j = 0; j < i; j++) {
203+
const otherShareView = shares[j];
204+
if (x !== otherShareView.getUint8(bytes)) continue;
205+
for (let k = 0; k < bytes; k++) {
206+
if (shareView.getUint8(k) !== otherShareView.getUint8(k)) {
207+
throw new Error("There are conflicting key shares", { cause: [
208+
parts[j],
209+
parts[i],
210+
] });
211+
}
212+
}
213+
return false;
214+
}
215+
return true;
216+
});
217+
if (neededParts == null) {
218+
neededParts = dataViews.length;
219+
} else if (dataViews.length < neededParts) {
220+
throw new Error("Not enough parts to reconstruct key");
221+
}
203222
for (let i = 0; i < bytes; i++) {
204223
result[i] = reconstructByte(
205224
Array.from({ length: neededParts }, (_, j) => {

packages/crypto/test/shamir.test.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,39 @@ it("should reconstruct a key", () => {
1313
);
1414
});
1515

16+
describe("should handle a key part being passed twice", () => {
17+
it("fail when there are not enough key parts", () => {
18+
assert.notStrictEqual(
19+
Buffer.from(shamir.reconstruct([
20+
Buffer.from([0xef, 0x05, 0x70, 0x4a, 0xf2, 0xb2, 0xcd, 0x02]),
21+
Buffer.from([0x62, 0x1e, 0x41, 0x63, 0xfa, 0x5e, 0x0b, 0x1c]),
22+
Buffer.from([0xef, 0x05, 0x70, 0x4a, 0xf2, 0xb2, 0xcd, 0x02]),
23+
])).toString("hex"),
24+
Buffer.from("caritat").toString("hex"),
25+
);
26+
});
27+
it("throw if the are incompatible key parts", () => {
28+
assert.throws(
29+
() => shamir.reconstruct([
30+
Buffer.from([0xef, 0x05, 0x70, 0x4a, 0xf2, 0xb2, 0xcd, 0x02]),
31+
Buffer.from([0x62, 0x1e, 0x41, 0x63, 0xfa, 0x5e, 0x0b, 0x1c]),
32+
Buffer.from([0xc4, 0xc8, 0x3c, 0x22, 0x53, 0x05, 0x62, 0x02]),
33+
]), /There are conflicting key shares/);
34+
});
35+
it("succeed when there are enough key parts", () => {
36+
assert.strictEqual(
37+
Buffer.from(shamir.reconstruct([
38+
Buffer.from([0xef, 0x05, 0x70, 0x4a, 0xf2, 0xb2, 0xcd, 0x02]),
39+
Buffer.from([0x62, 0x1e, 0x41, 0x63, 0xfa, 0x5e, 0x0b, 0x1c]),
40+
Buffer.from([0xef, 0x05, 0x70, 0x4a, 0xf2, 0xb2, 0xcd, 0x02]),
41+
Buffer.from([0xc4, 0xc8, 0x3c, 0x22, 0x53, 0x05, 0x62, 0x0a]),
42+
Buffer.from([0xef, 0x05, 0x70, 0x4a, 0xf2, 0xb2, 0xcd, 0x02]),
43+
])).toString("hex"),
44+
Buffer.from("caritat").toString("hex"),
45+
);
46+
});
47+
});
48+
1649
const key = crypto.getRandomValues(new Uint8Array(256));
1750

1851
describe("should reconstruct single byte with enough shareholders", () => {
@@ -95,9 +128,9 @@ it("should fail reconstruct key from not enough shareholders", () => {
95128
it("should fail reconstruct key with duplicate shareholders", () => {
96129
assert.throws(
97130
() => {
98-
shamir.reconstruct([parts[1], parts[5], parts[1]]);
131+
shamir.reconstruct([parts[1], parts[5], parts[1]], 3);
99132
},
100-
{ message: "Div/0" },
133+
{ message: "Not enough parts to reconstruct key" },
101134
);
102135
});
103136

0 commit comments

Comments
 (0)