@bcts/shamir 1.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +48 -0
- package/README.md +11 -0
- package/dist/index.cjs +506 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +179 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +179 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +509 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +492 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +75 -0
- package/src/error.ts +51 -0
- package/src/hazmat.ts +287 -0
- package/src/index.ts +45 -0
- package/src/interpolate.ts +157 -0
- package/src/shamir.ts +185 -0
package/src/shamir.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Ported from bc-shamir-rust/src/shamir.rs
|
|
2
|
+
|
|
3
|
+
import { hmacSha256, memzero, memzeroVecVecU8 } from "@bcts/crypto";
|
|
4
|
+
import type { RandomNumberGenerator } from "@bcts/rand";
|
|
5
|
+
|
|
6
|
+
import { ShamirError, ShamirErrorType } from "./error.js";
|
|
7
|
+
import { MAX_SECRET_LEN, MAX_SHARE_COUNT, MIN_SECRET_LEN } from "./index.js";
|
|
8
|
+
import { interpolate } from "./interpolate.js";
|
|
9
|
+
|
|
10
|
+
const SECRET_INDEX = 255;
|
|
11
|
+
const DIGEST_INDEX = 254;
|
|
12
|
+
|
|
13
|
+
function createDigest(randomData: Uint8Array, sharedSecret: Uint8Array): Uint8Array {
|
|
14
|
+
return hmacSha256(randomData, sharedSecret);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function validateParameters(threshold: number, shareCount: number, secretLength: number): void {
|
|
18
|
+
if (shareCount > MAX_SHARE_COUNT) {
|
|
19
|
+
throw new ShamirError(ShamirErrorType.TooManyShares);
|
|
20
|
+
} else if (threshold < 1 || threshold > shareCount) {
|
|
21
|
+
throw new ShamirError(ShamirErrorType.InvalidThreshold);
|
|
22
|
+
} else if (secretLength > MAX_SECRET_LEN) {
|
|
23
|
+
throw new ShamirError(ShamirErrorType.SecretTooLong);
|
|
24
|
+
} else if (secretLength < MIN_SECRET_LEN) {
|
|
25
|
+
throw new ShamirError(ShamirErrorType.SecretTooShort);
|
|
26
|
+
} else if ((secretLength & 1) !== 0) {
|
|
27
|
+
throw new ShamirError(ShamirErrorType.SecretNotEvenLen);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Splits a secret into shares using the Shamir secret sharing algorithm.
|
|
33
|
+
*
|
|
34
|
+
* @param threshold - The minimum number of shares required to reconstruct the
|
|
35
|
+
* secret. Must be greater than or equal to 1 and less than or equal to
|
|
36
|
+
* shareCount.
|
|
37
|
+
* @param shareCount - The total number of shares to generate. Must be at least
|
|
38
|
+
* threshold and less than or equal to MAX_SHARE_COUNT.
|
|
39
|
+
* @param secret - A Uint8Array containing the secret to be split. Must be at
|
|
40
|
+
* least MIN_SECRET_LEN bytes long and at most MAX_SECRET_LEN bytes long.
|
|
41
|
+
* The length must be an even number.
|
|
42
|
+
* @param randomGenerator - An implementation of the RandomNumberGenerator
|
|
43
|
+
* interface, used to generate random data.
|
|
44
|
+
* @returns An array of Uint8Array representing the shares of the secret.
|
|
45
|
+
* @throws ShamirError if parameters are invalid
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { splitSecret } from "@bcts/shamir";
|
|
50
|
+
* import { SecureRandomNumberGenerator } from "@bcts/rand";
|
|
51
|
+
*
|
|
52
|
+
* const threshold = 2;
|
|
53
|
+
* const shareCount = 3;
|
|
54
|
+
* const secret = new TextEncoder().encode("my secret belongs to me.");
|
|
55
|
+
* const rng = new SecureRandomNumberGenerator();
|
|
56
|
+
*
|
|
57
|
+
* const shares = splitSecret(threshold, shareCount, secret, rng);
|
|
58
|
+
* console.log(shares.length); // 3
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function splitSecret(
|
|
62
|
+
threshold: number,
|
|
63
|
+
shareCount: number,
|
|
64
|
+
secret: Uint8Array,
|
|
65
|
+
randomGenerator: RandomNumberGenerator,
|
|
66
|
+
): Uint8Array[] {
|
|
67
|
+
validateParameters(threshold, shareCount, secret.length);
|
|
68
|
+
|
|
69
|
+
if (threshold === 1) {
|
|
70
|
+
// just return shareCount copies of the secret
|
|
71
|
+
const result: Uint8Array[] = [];
|
|
72
|
+
for (let i = 0; i < shareCount; i++) {
|
|
73
|
+
result.push(new Uint8Array(secret));
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
} else {
|
|
77
|
+
const x = new Uint8Array(shareCount);
|
|
78
|
+
const y: Uint8Array[] = [];
|
|
79
|
+
for (let i = 0; i < shareCount; i++) {
|
|
80
|
+
y.push(new Uint8Array(secret.length));
|
|
81
|
+
}
|
|
82
|
+
let n = 0;
|
|
83
|
+
const result: Uint8Array[] = [];
|
|
84
|
+
for (let i = 0; i < shareCount; i++) {
|
|
85
|
+
result.push(new Uint8Array(secret.length));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (let index = 0; index < threshold - 2; index++) {
|
|
89
|
+
randomGenerator.fillRandomData(result[index]);
|
|
90
|
+
x[n] = index;
|
|
91
|
+
y[n].set(result[index]);
|
|
92
|
+
n++;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// generate secret_length - 4 bytes worth of random data
|
|
96
|
+
const digest = new Uint8Array(secret.length);
|
|
97
|
+
randomGenerator.fillRandomData(digest.subarray(4));
|
|
98
|
+
// put 4 bytes of digest at the top of the digest array
|
|
99
|
+
const d = createDigest(digest.subarray(4), secret);
|
|
100
|
+
digest.set(d.subarray(0, 4), 0);
|
|
101
|
+
x[n] = DIGEST_INDEX;
|
|
102
|
+
y[n].set(digest);
|
|
103
|
+
n++;
|
|
104
|
+
|
|
105
|
+
x[n] = SECRET_INDEX;
|
|
106
|
+
y[n].set(secret);
|
|
107
|
+
n++;
|
|
108
|
+
|
|
109
|
+
for (let index = threshold - 2; index < shareCount; index++) {
|
|
110
|
+
const v = interpolate(n, x, secret.length, y, index);
|
|
111
|
+
result[index].set(v);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// clean up stack
|
|
115
|
+
memzero(digest);
|
|
116
|
+
memzero(x);
|
|
117
|
+
memzeroVecVecU8(y);
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Recovers the secret from the given shares using the Shamir secret sharing
|
|
125
|
+
* algorithm.
|
|
126
|
+
*
|
|
127
|
+
* @param indexes - An array of indexes of the shares to be used for recovering
|
|
128
|
+
* the secret. These are the indexes of the shares returned by splitSecret.
|
|
129
|
+
* @param shares - An array of shares of the secret matching the indexes in
|
|
130
|
+
* indexes. These are the shares returned by splitSecret.
|
|
131
|
+
* @returns A Uint8Array representing the recovered secret.
|
|
132
|
+
* @throws ShamirError if parameters are invalid or checksum verification fails
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* import { recoverSecret } from "@bcts/shamir";
|
|
137
|
+
*
|
|
138
|
+
* const indexes = [0, 2];
|
|
139
|
+
* const shares = [
|
|
140
|
+
* new Uint8Array([47, 165, 102, 232, ...]),
|
|
141
|
+
* new Uint8Array([221, 174, 116, 201, ...]),
|
|
142
|
+
* ];
|
|
143
|
+
*
|
|
144
|
+
* const secret = recoverSecret(indexes, shares);
|
|
145
|
+
* console.log(new TextDecoder().decode(secret)); // "my secret belongs to me."
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export function recoverSecret(indexes: number[], shares: Uint8Array[]): Uint8Array {
|
|
149
|
+
const threshold = shares.length;
|
|
150
|
+
if (threshold === 0 || indexes.length !== threshold) {
|
|
151
|
+
throw new ShamirError(ShamirErrorType.InvalidThreshold);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const shareLength = shares[0].length;
|
|
155
|
+
validateParameters(threshold, threshold, shareLength);
|
|
156
|
+
|
|
157
|
+
const allSameLength = shares.every((share) => share.length === shareLength);
|
|
158
|
+
if (!allSameLength) {
|
|
159
|
+
throw new ShamirError(ShamirErrorType.SharesUnequalLength);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (threshold === 1) {
|
|
163
|
+
return new Uint8Array(shares[0]);
|
|
164
|
+
} else {
|
|
165
|
+
const indexesU8 = new Uint8Array(indexes);
|
|
166
|
+
|
|
167
|
+
const digest = interpolate(threshold, indexesU8, shareLength, shares, DIGEST_INDEX);
|
|
168
|
+
const secret = interpolate(threshold, indexesU8, shareLength, shares, SECRET_INDEX);
|
|
169
|
+
const verify = createDigest(digest.subarray(4), secret);
|
|
170
|
+
|
|
171
|
+
let valid = true;
|
|
172
|
+
for (let i = 0; i < 4; i++) {
|
|
173
|
+
valid = valid && digest[i] === verify[i];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
memzero(digest);
|
|
177
|
+
memzero(verify);
|
|
178
|
+
|
|
179
|
+
if (!valid) {
|
|
180
|
+
throw new ShamirError(ShamirErrorType.ChecksumFailure);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return secret;
|
|
184
|
+
}
|
|
185
|
+
}
|