threshold-elgamal 0.1.0

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.
@@ -0,0 +1,157 @@
1
+ import { modPow, modInv } from 'bigint-mod-arith';
2
+
3
+ import { GROUPS } from './constants';
4
+ import type { EncryptedMessage, PartyKeyPair } from './types';
5
+ import { generatePolynomial } from './utils';
6
+
7
+ /**
8
+ * Retrieves the group parameters for a given prime bit length.
9
+ *
10
+ * @param {2048 | 3072 | 4096} primeBits - The bit length of the prime modulus (2048, 3072, or 4096).
11
+ * @returns {Object} The group parameters including prime and generator.
12
+ */
13
+ export const getGroup = (
14
+ primeBits: 2048 | 3072 | 4096,
15
+ ): { prime: bigint; generator: bigint } => {
16
+ switch (primeBits) {
17
+ case 2048:
18
+ return GROUPS.ffdhe2048;
19
+ case 3072:
20
+ return GROUPS.ffdhe3072;
21
+ case 4096:
22
+ return GROUPS.ffdhe4096;
23
+ default:
24
+ throw new Error('Unsupported bit length');
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Evaluates a polynomial at a given point using modular arithmetic.
30
+ *
31
+ * @param {bigint[]} polynomial - The coefficients of the polynomial.
32
+ * @param {number} x - The point at which to evaluate the polynomial.
33
+ * @param {bigint} prime - The prime modulus.
34
+ * @returns {bigint} The result of the polynomial evaluation.
35
+ */
36
+ export const evaluatePolynomial = (
37
+ polynomial: bigint[],
38
+ x: number,
39
+ prime: bigint,
40
+ ): bigint => {
41
+ let result = 0n;
42
+ for (let i = 0; i < polynomial.length; i++) {
43
+ result = (result + polynomial[i] * BigInt(x) ** BigInt(i)) % prime;
44
+ }
45
+ return result;
46
+ };
47
+
48
+ /**
49
+ * Generates a single key share for a participant in a threshold ElGamal cryptosystem.
50
+ *
51
+ * @param {number} index - The unique index of the participant (starting from 1).
52
+ * @param {number} threshold - The minimum number of key shares required for decryption.
53
+ * @param {2048 | 3072 | 4096} primeBits - The bit length of the prime modulus (default: 2048).
54
+ * @returns {PartyKeyPair} The key share containing a private and public key share for the participant.
55
+ */
56
+ export const generateSingleKeyShare = (
57
+ index: number,
58
+ threshold: number,
59
+ primeBits: 2048 | 3072 | 4096 = 2048,
60
+ ): PartyKeyPair => {
61
+ const group = getGroup(primeBits);
62
+ const prime = group.prime;
63
+ const generator = group.generator;
64
+ const polynomial = generatePolynomial(threshold, prime);
65
+ let partyPrivateKey = evaluatePolynomial(polynomial, index, prime);
66
+ // Ensure non-zero private key, adjusting index if necessary
67
+ while (partyPrivateKey === 0n) {
68
+ partyPrivateKey = evaluatePolynomial(polynomial, index + 1, prime);
69
+ }
70
+ const partyPublicKey = modPow(generator, partyPrivateKey, prime);
71
+
72
+ return { partyPrivateKey, partyPublicKey };
73
+ };
74
+
75
+ /**
76
+ * Generates key shares for a threshold ElGamal cryptosystem.
77
+ *
78
+ * @param {number} n - The total number of key shares.
79
+ * @param {number} threshold - The minimum number of key shares required for decryption.
80
+ * @param {2048 | 3072 | 4096} primeBits - The bit length of the prime modulus (default: 2048).
81
+ * @returns {PartyKeyPair[]} An array of key shares, each containing a private and public key share.
82
+ */
83
+ export const generateKeyShares = (
84
+ n: number,
85
+ threshold: number,
86
+ primeBits: 2048 | 3072 | 4096 = 2048,
87
+ ): PartyKeyPair[] => {
88
+ const keyShares = [];
89
+ for (let i = 1; i <= n; i++) {
90
+ const keyShare = generateSingleKeyShare(i, threshold, primeBits);
91
+ keyShares.push(keyShare);
92
+ }
93
+ return keyShares;
94
+ };
95
+
96
+ /**
97
+ * Combines multiple public keys into a single public key.
98
+ *
99
+ * @param {bigint[]} publicKeys - An array of public keys to combine.
100
+ * @param {bigint} prime - The prime modulus used in the ElGamal system.
101
+ * @returns {bigint} The combined public key.
102
+ */
103
+ export const combinePublicKeys = (
104
+ publicKeys: bigint[],
105
+ prime: bigint,
106
+ ): bigint => publicKeys.reduce((acc, current) => (acc * current) % prime, 1n);
107
+
108
+ /**
109
+ * Performs a partial decryption on a ciphertext using an individual's private key share.
110
+ *
111
+ * @param {EncryptedMessage} encryptedMessage - The encrypted secret.
112
+ * @param {bigint} partyPrivateKey - The private key share of the decrypting party.
113
+ * @param {bigint} prime - The prime modulus used in the ElGamal system.
114
+ * @returns {bigint} The result of the partial decryption.
115
+ */
116
+ export const createDecryptionShare = (
117
+ encryptedMessage: EncryptedMessage,
118
+ partyPrivateKey: bigint,
119
+ prime: bigint,
120
+ ): bigint => modPow(encryptedMessage.c1, partyPrivateKey, prime);
121
+
122
+ /**
123
+ * Combines partial decryptions from multiple parties into a single decryption factor.
124
+ *
125
+ * @param {bigint[]} decryptionShares - An array of partial decryption results.
126
+ * @param {bigint} prime - The prime modulus used in the ElGamal system.
127
+ * @returns {bigint} The combined decryption factor.
128
+ */
129
+ export const combineDecryptionShares = (
130
+ decryptionShares: bigint[],
131
+ prime: bigint,
132
+ ): bigint => {
133
+ let result = 1n;
134
+ for (const partialDecryption of decryptionShares) {
135
+ result = (result * partialDecryption) % prime;
136
+ }
137
+ return result;
138
+ };
139
+
140
+ /**
141
+ * Decrypts an encrypted secret using the combined partial decryptions in a threshold ElGamal scheme.
142
+ *
143
+ * @param {{ c1: bigint; c2: bigint }} encryptedMessage - The encrypted secret components.
144
+ * @param {bigint} combinedDecryptionShares - The combined partial decryptions from all parties.
145
+ * @param {bigint} prime - The prime modulus used in the ElGamal system.
146
+ * @returns {number} The decrypted secret, assuming it was small enough to be directly encrypted.
147
+ */
148
+ export const thresholdDecrypt = (
149
+ encryptedMessage: { c1: bigint; c2: bigint },
150
+ combinedDecryptionShares: bigint,
151
+ prime: bigint,
152
+ ): number => {
153
+ const combinedDecryptionInverse = modInv(combinedDecryptionShares, prime);
154
+ const plaintext: bigint =
155
+ (encryptedMessage.c2 * combinedDecryptionInverse) % prime;
156
+ return Number(plaintext);
157
+ };
@@ -0,0 +1,13 @@
1
+ declare module 'random-bigint' {
2
+ /**
3
+ * Generates a random BigInt in the range 0..2**bits-1.
4
+ * @param bits Number of bits.
5
+ * @param cb Optional callback for asynchronous operation.
6
+ */
7
+ function random(
8
+ bits: number,
9
+ cb?: (err: Error | null, num: bigint) => void,
10
+ ): bigint;
11
+
12
+ export = random;
13
+ }
package/src/types.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type EncryptedMessage = {
2
+ c1: bigint;
3
+ c2: bigint;
4
+ };
5
+
6
+ export type Parameters = {
7
+ prime: bigint;
8
+ generator: bigint;
9
+ publicKey: bigint;
10
+ privateKey: bigint;
11
+ };
12
+
13
+ export type KeyPair = {
14
+ privateKey: bigint;
15
+ publicKey: bigint;
16
+ };
17
+
18
+ export type PartyKeyPair = {
19
+ partyPrivateKey: bigint;
20
+ partyPublicKey: bigint;
21
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,61 @@
1
+ import randomBigint from 'random-bigint';
2
+
3
+ import type { EncryptedMessage } from './types';
4
+
5
+ /**
6
+ * Generates a random bigint within a specified range.
7
+ * @param {bigint} min - The minimum value (inclusive).
8
+ * @param {bigint} max - The maximum value (exclusive).
9
+ * @returns {bigint} A random bigint within the specified range.
10
+ */
11
+ export const getRandomBigIntegerInRange = (
12
+ min: bigint,
13
+ max: bigint,
14
+ ): bigint => {
15
+ const range = max - min + 1n;
16
+ // Determine the number of bits needed for the range
17
+ const bitsNeeded = range.toString(2).length;
18
+ // Generate a random bigint within the calculated bits
19
+ let num = randomBigint(bitsNeeded);
20
+ // Adjust the number to our range
21
+ num = num % range;
22
+ // Add the minimum to align with our desired range
23
+ return min + num;
24
+ };
25
+
26
+ /**
27
+ * Performs homomorphic multiplication on two encrypted values, allowing for encrypted arithmetic operations.
28
+ * @param {EncryptedMessage} value1 - The first encrypted value.
29
+ * @param {EncryptedMessage} value2 - The second encrypted value.
30
+ * @param {bigint} prime - The prime modulus used in the encryption system.
31
+ * @returns {EncryptedMessage} The result of the multiplication, as a new encrypted message.
32
+ */
33
+ export const multiplyEncryptedValues = (
34
+ value1: EncryptedMessage,
35
+ value2: EncryptedMessage,
36
+ prime: bigint,
37
+ ): EncryptedMessage => {
38
+ const c1Multiplied = (value1.c1 * value2.c1) % prime;
39
+ const c2Multiplied = (value1.c2 * value2.c2) % prime;
40
+
41
+ return { c1: c1Multiplied, c2: c2Multiplied };
42
+ };
43
+
44
+ /**
45
+ * Generates a random polynomial of a specified degree, to be used in Shamir's Secret Sharing scheme.
46
+ * The polynomial is of the form f(x) = a0 + a1*x + a2*x^2 + ... + a_{threshold-1}*x^{threshold-1},
47
+ * where a0 is the "master" secret, and the rest of the coefficients are randomly chosen.
48
+ * @param {number} threshold - The degree of the polynomial (also, the number of shares required to reconstruct the secret).
49
+ * @param {bigint} prime - The prime modulus used in the system, to ensure operations are performed in a finite field.
50
+ * @returns {bigint[]} An array representing the polynomial coefficients `[a0, a1, ..., a_{threshold-1}]`.
51
+ */
52
+ export const generatePolynomial = (
53
+ threshold: number,
54
+ prime: bigint,
55
+ ): bigint[] => {
56
+ const polynomial = [getRandomBigIntegerInRange(2n, prime - 1n)]; // constant term is the "master" private key
57
+ for (let i = 1; i < threshold; i++) {
58
+ polynomial.push(getRandomBigIntegerInRange(0n, prime - 1n)); // random coefficients
59
+ }
60
+ return polynomial;
61
+ };
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "declaration": true,
6
+ "sourceMap": false,
7
+ "removeComments": false
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": [
11
+ "node_modules",
12
+ "**/*.test.ts",
13
+ "**/*.spec.ts",
14
+ "eslint.config.js",
15
+ "jsdoc.config.js"
16
+ ]
17
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": "./src/",
4
+ "lib": ["ES2022"],
5
+ "target": "esnext",
6
+ "module": "ESNext",
7
+ "moduleResolution": "Node",
8
+ "outDir": "./dist/",
9
+ "removeComments": true,
10
+ "declaration": false,
11
+ "allowSyntheticDefaultImports": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "strict": true,
15
+ "sourceMap": true,
16
+ "allowJs": true,
17
+ "noEmit": true,
18
+ "esModuleInterop": true,
19
+ "isolatedModules": true,
20
+ "skipLibCheck": true,
21
+ "noUnusedParameters": true,
22
+ "noUnusedLocals": true
23
+ },
24
+ "ts-node": {
25
+ "esm": true,
26
+ "experimentalSpecifierResolution": "node"
27
+ },
28
+ "include": ["src/**/*", "eslint.config.js", "jsdoc.config.js"]
29
+ }