Skip to content

Commit bc20f01

Browse files
committed
feat: added add_const, sub, sub_const, mul_const
1 parent b110d8a commit bc20f01

File tree

4 files changed

+203
-23
lines changed

4 files changed

+203
-23
lines changed

contracts/BigNum.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ library BigNum {
1616
/// @notice the value for number 0 of a BigNumber instance.
1717
bytes constant ZERO = hex"0000000000000000000000000000000000000000000000000000000000000000";
1818
/// @notice the value for number 1 of a BigNumber instance.
19-
bytes constant ONE = hex"0000000000000000000000000000000000000000000000000000000000000001";
19+
bytes constant ONE = hex"0000000000000000000000000000000000000000000000000000000000000001";
2020
/// @notice the value for number 2 of a BigNumber instance.
21-
bytes constant TWO = hex"0000000000000000000000000000000000000000000000000000000000000002";
21+
bytes constant TWO = hex"0000000000000000000000000000000000000000000000000000000000000002";
2222

2323
// ***************** BEGIN EXPOSED MANAGEMENT FUNCTIONS ******************
2424
/** @notice verify a BN instance

contracts/Paillier.sol

+121-11
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ struct Ciphertext {
1414
/// @notice A struct to represent a public key
1515
/// @dev The public key is stored as a byte array
1616
struct PublicKey {
17-
bytes value;
17+
bytes n;
18+
bytes g;
1819
}
1920

2021
/// @title PrivateKey Struct
@@ -25,7 +26,7 @@ struct PrivateKey {
2526
}
2627

2728
/// @title Paillier Cryptosystem Implementation
28-
/// @author The developer
29+
/// @author lodge
2930
/// @notice This contract provides basic operations for the Paillier cryptosystem
3031
/// @dev Uses the BigNum library for large number operations
3132
contract Paillier {
@@ -54,9 +55,9 @@ contract Paillier {
5455
BigNum.bitLength(b.value)
5556
);
5657
BigNumber memory pub_n = BigNumber(
57-
publicKey.value,
58+
publicKey.n,
5859
false,
59-
BigNum.bitLength(publicKey.value)
60+
BigNum.bitLength(publicKey.n)
6061
);
6162

6263
// Calculate the encrypted sum as enc_a * enc_b % pub_n^2
@@ -68,13 +69,122 @@ contract Paillier {
6869
return enc_sum;
6970
}
7071

72+
/// @notice Adds a plaintext value to an encrypted value
73+
/// @dev The function computes (Enc(a) * g^b % n^2) to add plaintext b to the encrypted value Enc(a)
74+
/// @param a The encrypted value as a Ciphertext
75+
/// @param b The plaintext value as a uint256
76+
/// @param publicKey The public key as a PublicKey
77+
/// @return enc_result The new encrypted value as a BigNumber
78+
function add_const(
79+
Ciphertext calldata a,
80+
uint256 b,
81+
PublicKey calldata publicKey
82+
) public view returns (BigNumber memory) {
83+
BigNumber memory enc_a = BigNumber(
84+
a.value,
85+
false,
86+
BigNum.bitLength(a.value)
87+
);
88+
BigNumber memory pub_n = BigNumber(
89+
publicKey.n,
90+
false,
91+
BigNum.bitLength(publicKey.n)
92+
);
93+
BigNumber memory g = BigNumber(
94+
publicKey.g,
95+
false,
96+
BigNum.bitLength(publicKey.g)
97+
);
98+
BigNumber memory enc_result = BigNum.mod(
99+
BigNum.mul(enc_a, BigNum.pow(g, b)),
100+
BigNum.pow(pub_n, 2)
101+
);
102+
103+
return enc_result;
104+
}
105+
106+
/// @notice Subtracts one encrypted value from another
107+
/// @dev The function computes Enc(a) * Enc(b)^(-1) % n^2 by using Enc(b)^(n-1)
108+
/// @param a The first encrypted value as a Ciphertext
109+
/// @param b The second encrypted value as a Ciphertext
110+
/// @param publicKey The public key as a PublicKey
111+
/// @return enc_result The result of the subtraction as a BigNumber
112+
function sub(
113+
Ciphertext calldata a,
114+
Ciphertext calldata b,
115+
PublicKey calldata publicKey
116+
) public view returns (BigNumber memory) {
117+
BigNumber memory enc_a = BigNumber(
118+
a.value,
119+
false,
120+
BigNum.bitLength(a.value)
121+
);
122+
BigNumber memory enc_b = BigNumber(
123+
b.value,
124+
false,
125+
BigNum.bitLength(b.value)
126+
);
127+
BigNumber memory pub_n = BigNumber(
128+
publicKey.n,
129+
false,
130+
BigNum.bitLength(publicKey.n)
131+
);
132+
133+
BigNumber memory modulus = BigNum.pow(pub_n, 2);
134+
BigNumber memory neg_enc_b = BigNum.modexp(
135+
enc_b,
136+
BigNum.sub(pub_n, BigNum.one()),
137+
modulus
138+
);
139+
140+
BigNumber memory enc_result = BigNum.mod(enc_a.mul(neg_enc_b), modulus);
141+
return enc_result;
142+
}
143+
144+
/// @notice Subtracts a plaintext constant from an encrypted value
145+
/// @dev The function computes Enc(a) * g^(-b) % n^2 by using g^(n-1)
146+
/// @param a The encrypted value as a Ciphertext
147+
/// @param b The plaintext constant as an int256
148+
/// @param publicKey The public key as a PublicKey
149+
/// @return enc_result The result of the subtraction as a BigNumber
150+
function sub_const(
151+
Ciphertext calldata a,
152+
int256 b,
153+
PublicKey calldata publicKey
154+
) public view returns (BigNumber memory) {
155+
BigNumber memory enc_a = BigNumber(
156+
a.value,
157+
false,
158+
BigNum.bitLength(a.value)
159+
);
160+
BigNumber memory pub_n = BigNumber(
161+
publicKey.n,
162+
false,
163+
BigNum.bitLength(publicKey.n)
164+
);
165+
BigNumber memory g = BigNumber(
166+
publicKey.g,
167+
false,
168+
BigNum.bitLength(publicKey.g)
169+
);
170+
171+
BigNumber memory bb = BigNumber(abi.encodePacked(b), true, 256);
172+
BigNumber memory inverse = BigNum.mod(bb, pub_n);
173+
BigNumber memory modulus = BigNum.pow(pub_n, 2);
174+
BigNumber memory enc_result = enc_a
175+
.mul(BigNum.modexp(g, inverse, modulus))
176+
.mod(modulus);
177+
178+
return enc_result;
179+
}
180+
71181
/// @notice Multiplies an encrypted value by a plaintext constant
72182
/// @dev The encrypted value is exponentiated to the plaintext constant and then taken modulo n^2
73183
/// @param a The encrypted value in bytes
74184
/// @param b The plaintext constant
75185
/// @param publicKey The public key in bytes
76186
/// @return enc_result The encrypted result as a BigNumber
77-
function mul(
187+
function mul_const(
78188
Ciphertext calldata a,
79189
uint256 b,
80190
PublicKey calldata publicKey
@@ -86,9 +196,9 @@ contract Paillier {
86196
BigNum.bitLength(a.value)
87197
);
88198
BigNumber memory pub_n = BigNumber(
89-
publicKey.value,
199+
publicKey.n,
90200
false,
91-
BigNum.bitLength(publicKey.value)
201+
BigNum.bitLength(publicKey.n)
92202
);
93203

94204
// Calculate the encrypted result as enc_value^b % pub_n^2
@@ -112,9 +222,9 @@ contract Paillier {
112222
// Create BigNumber representations for the random value and the public key
113223
BigNumber memory rand = BigNumber(rnd, false, BigNum.bitLength(rnd));
114224
BigNumber memory pub_n = BigNumber(
115-
publicKey.value,
225+
publicKey.n,
116226
false,
117-
BigNum.bitLength(publicKey.value)
227+
BigNum.bitLength(publicKey.n)
118228
);
119229

120230
// Calculate the encrypted zero as r^n % n^2
@@ -149,9 +259,9 @@ contract Paillier {
149259
BigNum.bitLength(privateKey.value)
150260
);
151261
BigNumber memory n = BigNumber(
152-
publicKey.value,
262+
publicKey.n,
153263
false,
154-
BigNum.bitLength(publicKey.value)
264+
BigNum.bitLength(publicKey.n)
155265
);
156266

157267
// Decrypt the value using private key lambda

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "paillier-contracts",
3-
"version": "1.0.4",
3+
"version": "1.1.0",
44
"description": "Paillier homomorphic encryption contract",
55
"license": "The Unlicense",
66
"author": {
@@ -24,4 +24,4 @@
2424
"bigint-conversion": "^2.4.3",
2525
"paillier-bigint": "^3.4.3"
2626
}
27-
}
27+
}

test/Paillier.ts

+78-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { expect } from "chai";
55

66
// Public key
77
interface PublicKey {
8-
value: string;
8+
n: string;
9+
g: string;
910
}
1011

1112
// Ciphertext
@@ -29,14 +30,15 @@ describe("Paillier", function () {
2930
};
3031

3132
// Public key
32-
const pub_n: PublicKey = {
33-
value: ethers.toBeHex(publicKey.n),
33+
const pubKey: PublicKey = {
34+
n: ethers.toBeHex(publicKey.n),
35+
g: ethers.toBeHex(publicKey.g),
3436
};
3537

3638
// bit length will differ to what has been stated in this script.
3739
// if using 256-bit key, bit_length will be 264 as "0x" prefix may have been factored in
3840
// Now lets deploy the contract and test the addition
39-
const enc_sum = await Paillier.add(enc_a, enc_b, pub_n);
41+
const enc_sum = await Paillier.add(enc_a, enc_b, pubKey);
4042
const enc_sum_int = bigIntConversion.hexToBigint(enc_sum[0]);
4143

4244
// Conversion to int for convenience
@@ -47,6 +49,68 @@ describe("Paillier", function () {
4749
expect(dec_sum).to.equal(3);
4850
});
4951

52+
it("should add a ciphertext and plaintext", async function () {
53+
const { publicKey, privateKey } =
54+
await paillierBigint.generateRandomKeys(256);
55+
const Paillier = await ethers.deployContract("Paillier");
56+
const a: bigint = BigInt(1);
57+
const b: bigint = BigInt(2);
58+
const enc_a = { value: ethers.toBeHex(publicKey.encrypt(a)) };
59+
const enc_b = { value: ethers.toBeHex(publicKey.encrypt(b)) };
60+
61+
// Public key
62+
const pubKey: PublicKey = {
63+
n: ethers.toBeHex(publicKey.n),
64+
g: ethers.toBeHex(publicKey.g),
65+
};
66+
const enc_sum = await Paillier.add_const(enc_a, b, pubKey);
67+
const enc_sum_int = bigIntConversion.hexToBigint(enc_sum[0]);
68+
const dec_sum = Number(privateKey.decrypt(enc_sum_int));
69+
console.log("Decrypted Sum:", dec_sum);
70+
expect(dec_sum).to.equal(3);
71+
});
72+
73+
it("should subtract 2 ciphertexts", async function () {
74+
const { publicKey, privateKey } =
75+
await paillierBigint.generateRandomKeys(256);
76+
const Paillier = await ethers.deployContract("Paillier");
77+
const a: bigint = BigInt(5);
78+
const b: bigint = BigInt(2);
79+
const enc_a = { value: ethers.toBeHex(publicKey.encrypt(a)) };
80+
const enc_b = { value: ethers.toBeHex(publicKey.encrypt(b)) };
81+
82+
// Public key
83+
const pubKey: PublicKey = {
84+
n: ethers.toBeHex(publicKey.n),
85+
g: ethers.toBeHex(publicKey.g),
86+
};
87+
const enc_diff = await Paillier.sub(enc_a, enc_b, pubKey);
88+
const enc_diff_int = bigIntConversion.hexToBigint(enc_diff[0]);
89+
const dec_diff = Number(privateKey.decrypt(enc_diff_int));
90+
console.log("Decrypted Difference:", dec_diff);
91+
expect(dec_diff).to.equal(3);
92+
});
93+
94+
it("should subtract a ciphertext and plaintext", async function () {
95+
const { publicKey, privateKey } =
96+
await paillierBigint.generateRandomKeys(256);
97+
const Paillier = await ethers.deployContract("Paillier");
98+
const a: bigint = BigInt(42);
99+
const b: bigint = BigInt(5);
100+
const enc_a = { value: ethers.toBeHex(publicKey.encrypt(a)) };
101+
102+
// Public key
103+
const pubKey: PublicKey = {
104+
n: ethers.toBeHex(publicKey.n),
105+
g: ethers.toBeHex(publicKey.g),
106+
};
107+
const enc_diff = await Paillier.sub_const(enc_a, b, pubKey);
108+
const enc_diff_int = bigIntConversion.hexToBigint(enc_diff[0]);
109+
const dec_diff = Number(privateKey.decrypt(enc_diff_int));
110+
console.log("Decrypted Difference:", dec_diff);
111+
expect(dec_diff).to.equal(37);
112+
});
113+
50114
it("should encrypt zero", async function () {
51115
const { publicKey, privateKey } =
52116
await paillierBigint.generateRandomKeys(256);
@@ -55,8 +119,11 @@ describe("Paillier", function () {
55119
const rand = ethers.toBeHex(Math.floor(Math.random() * 1000000));
56120

57121
// Public key
58-
const pub_n = { value: ethers.toBeHex(publicKey.n) };
59-
const enc_zero = await Paillier.encryptZero(rand, pub_n);
122+
const pubKey: PublicKey = {
123+
n: ethers.toBeHex(publicKey.n),
124+
g: ethers.toBeHex(publicKey.g),
125+
};
126+
const enc_zero = await Paillier.encryptZero(rand, pubKey);
60127
const enc_zero_int = bigIntConversion.hexToBigint(enc_zero[0]);
61128
const dec_zero = Number(privateKey.decrypt(enc_zero_int));
62129
expect(dec_zero).to.equal(0);
@@ -72,8 +139,11 @@ describe("Paillier", function () {
72139
const enc_a = { value: ethers.toBeHex(publicKey.encrypt(a)) };
73140

74141
// Public key
75-
const pub_n = { value: ethers.toBeHex(publicKey.n) };
76-
const enc_scalar = await Paillier.mul(enc_a, b, pub_n);
142+
const pubKey: PublicKey = {
143+
n: ethers.toBeHex(publicKey.n),
144+
g: ethers.toBeHex(publicKey.g),
145+
};
146+
const enc_scalar = await Paillier.mul_const(enc_a, b, pubKey);
77147

78148
// returns tuple so get first index
79149
const enc_scalar_int = bigIntConversion.hexToBigint(enc_scalar[0]);

0 commit comments

Comments
 (0)