Skip to content

Commit 475c79a

Browse files
committed
feat: strongly typed ciphertext, public and private keys
1 parent cff88b9 commit 475c79a

File tree

5 files changed

+116
-57
lines changed

5 files changed

+116
-57
lines changed

README.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
# Paillier Solidity Smart Contract
2+
23
This Solidity smart contract provides functionalities for performing homomorphic encryption using the Paillier cryptosystem on the Ethereum blockchain. Homomorphic encryption allows computations to be performed on encrypted data without decrypting it first, providing privacy and security benefits.
34

45
# Features
6+
57
- Addition of two encrypted values.
68
- Multiplication of an encrypted value by a plaintext constant.
79
- Encryption of zero.
810

911
# Installation
12+
1013
To use this smart contract, you need to deploy it on the Ethereum blockchain. You'll also need to import the BigNumbers.sol library for handling large numbers.
1114

1215
# Usage
16+
1317
1. Deploy the PaillierSolidity contract on the Ethereum blockchain.
1418
2. Import the contract into your Solidity code.
1519
3. Use the provided functions to perform homomorphic encryption operations.
@@ -19,6 +23,7 @@ To use this smart contract, you need to deploy it on the Ethereum blockchain. Yo
1923
The Paillier cryptosystem, which is homomorphic with respect to addition and multiplication, can be applied in various blockchain-based applications that require privacy-preserving computation. Here are five possible applications with corresponding example Solidity code snippets:
2024

2125
## Private Voting System
26+
2227
In a voting system, votes can be encrypted using the Paillier cryptosystem. Each encrypted vote is a ciphertext representing either 0 or 1 (abstention or cast vote). The final tally can be computed without decrypting the individual votes.
2328

2429
```solidity
@@ -52,6 +57,7 @@ contract PrivateVoting {
5257
```
5358

5459
## Privacy-Preserving Payroll:
60+
5561
Payroll can be computed on-chain while preserving employees' salary privacy. Each employee's encrypted salary can be stored, and payroll totals can be computed without revealing individual salaries.
5662

5763
```solidity
@@ -86,6 +92,7 @@ contract PrivatePayroll {
8692
```
8793

8894
## Anonymous Donations:
95+
8996
In a charitable donation system, donors' contributions can be kept private. The total amount donated can be computed and verified without revealing individual contributions.
9097

9198
```solidity
@@ -119,6 +126,7 @@ contract AnonymousDonations {
119126
```
120127

121128
## Private Auction:
129+
122130
In a private auction, bids can be encrypted, and the winner can be determined without revealing other bidders' bid amounts.
123131

124132
```solidity
@@ -158,6 +166,7 @@ contract PrivateAuction {
158166
```
159167

160168
## Private Tax Calculation:
169+
161170
Tax calculations can be carried out on-chain while keeping individual taxpayers' incomes private. The total tax owed can be computed without revealing individual incomes.
162171

163172
```solidity
@@ -192,4 +201,8 @@ contract PrivateTaxCalculation {
192201
return total;
193202
}
194203
}
195-
```
204+
```
205+
206+
# References
207+
208+
https://github.com/jahali6128/paillier-solidity

contracts/Paillier.sol

+57-24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,27 @@ pragma solidity ^0.8.24;
33

44
import "./BigNum.sol";
55

6+
/// @title CipherText Struct
7+
/// @notice A struct to represent an encrypted value
8+
/// @dev The encrypted value is stored as a byte array
9+
struct Ciphertext {
10+
bytes value;
11+
}
12+
13+
/// @title PublicKey Struct
14+
/// @notice A struct to represent a public key
15+
/// @dev The public key is stored as a byte array
16+
struct PublicKey {
17+
bytes value;
18+
}
19+
20+
/// @title PrivateKey Struct
21+
/// @notice A struct to represent a private key
22+
/// @dev The private key is stored as a byte array
23+
struct PrivateKey {
24+
bytes value;
25+
}
26+
627
/// @title Paillier Cryptosystem Implementation
728
/// @author The developer
829
/// @notice This contract provides basic operations for the Paillier cryptosystem
@@ -17,17 +38,25 @@ contract Paillier {
1738
/// @param publicKey The public key in bytes
1839
/// @return enc_sum The encrypted sum as a BigNumber
1940
function add(
20-
bytes calldata a,
21-
bytes calldata b,
22-
bytes calldata publicKey
41+
Ciphertext calldata a,
42+
Ciphertext calldata b,
43+
PublicKey calldata publicKey
2344
) public view returns (BigNumber memory) {
2445
// Create BigNumber representations for the encrypted values and the public key
25-
BigNumber memory enc_a = BigNumber(a, false, BigNum.bitLength(a));
26-
BigNumber memory enc_b = BigNumber(b, false, BigNum.bitLength(b));
46+
BigNumber memory enc_a = BigNumber(
47+
a.value,
48+
false,
49+
BigNum.bitLength(a.value)
50+
);
51+
BigNumber memory enc_b = BigNumber(
52+
b.value,
53+
false,
54+
BigNum.bitLength(b.value)
55+
);
2756
BigNumber memory pub_n = BigNumber(
28-
publicKey,
57+
publicKey.value,
2958
false,
30-
BigNum.bitLength(publicKey)
59+
BigNum.bitLength(publicKey.value)
3160
);
3261

3362
// Calculate the encrypted sum as enc_a * enc_b % pub_n^2
@@ -46,16 +75,20 @@ contract Paillier {
4675
/// @param publicKey The public key in bytes
4776
/// @return enc_result The encrypted result as a BigNumber
4877
function mul(
49-
bytes calldata a,
78+
Ciphertext calldata a,
5079
uint256 b,
51-
bytes calldata publicKey
80+
PublicKey calldata publicKey
5281
) public view returns (BigNumber memory) {
5382
// Create BigNumber representations for the encrypted value and the public key
54-
BigNumber memory enc_value = BigNumber(a, false, BigNum.bitLength(a));
83+
BigNumber memory enc_value = BigNumber(
84+
a.value,
85+
false,
86+
BigNum.bitLength(a.value)
87+
);
5588
BigNumber memory pub_n = BigNumber(
56-
publicKey,
89+
publicKey.value,
5790
false,
58-
BigNum.bitLength(publicKey)
91+
BigNum.bitLength(publicKey.value)
5992
);
6093

6194
// Calculate the encrypted result as enc_value^b % pub_n^2
@@ -74,14 +107,14 @@ contract Paillier {
74107
/// @return enc_zero The encrypted zero as a BigNumber
75108
function encryptZero(
76109
bytes calldata rnd,
77-
bytes calldata publicKey
110+
PublicKey calldata publicKey
78111
) public view returns (BigNumber memory) {
79112
// Create BigNumber representations for the random value and the public key
80113
BigNumber memory rand = BigNumber(rnd, false, BigNum.bitLength(rnd));
81114
BigNumber memory pub_n = BigNumber(
82-
publicKey,
115+
publicKey.value,
83116
false,
84-
BigNum.bitLength(publicKey)
117+
BigNum.bitLength(publicKey.value)
85118
);
86119

87120
// Calculate the encrypted zero as r^n % n^2
@@ -100,25 +133,25 @@ contract Paillier {
100133
/// @param publicKey The public key in bytes
101134
/// @return decryptedValue The decrypted value as a BigNumber
102135
function decrypt(
103-
bytes calldata encValue,
104-
bytes calldata privateKey,
105-
bytes calldata publicKey
136+
Ciphertext calldata encValue,
137+
PrivateKey calldata privateKey,
138+
PublicKey calldata publicKey
106139
) public view returns (BigNumber memory) {
107140
// Create BigNumber representations for the encrypted value, private key, and public key
108141
BigNumber memory enc_value = BigNumber(
109-
encValue,
142+
encValue.value,
110143
false,
111-
BigNum.bitLength(encValue)
144+
BigNum.bitLength(encValue.value)
112145
);
113146
BigNumber memory lambda = BigNumber(
114-
privateKey,
147+
privateKey.value,
115148
false,
116-
BigNum.bitLength(privateKey)
149+
BigNum.bitLength(privateKey.value)
117150
);
118151
BigNumber memory n = BigNumber(
119-
publicKey,
152+
publicKey.value,
120153
false,
121-
BigNum.bitLength(publicKey)
154+
BigNum.bitLength(publicKey.value)
122155
);
123156

124157
// Decrypt the value using private key lambda

hardhat.config.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { HardhatUserConfig } from "hardhat/config";
22
import "@nomicfoundation/hardhat-toolbox";
33
require("@nomicfoundation/hardhat-chai-matchers");
44

5-
const mnemonic: string = "adapt mosquito move limb mobile illegal tree voyage juice mosquito burger raise father hope layer"
5+
const mnemonic: string =
6+
"adapt mosquito move limb mobile illegal tree voyage juice mosquito burger raise father hope layer";
67
const config: HardhatUserConfig = {
78
solidity: "0.8.25",
89
defaultNetwork: "hardhat",

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "paillier-contracts",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "Paillier homomorphic encryption contract",
55
"license": "The Unlicense",
66
"author": {

test/Paillier.ts

+42-30
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
1-
import * as paillierBigint from 'paillier-bigint';
2-
import * as bigIntConversion from 'bigint-conversion';
1+
import * as paillierBigint from "paillier-bigint";
2+
import * as bigIntConversion from "bigint-conversion";
33
import { ethers } from "hardhat";
44
import { expect } from "chai";
55

6-
describe("Paillier", function () {
6+
// Public key
7+
interface PublicKey {
8+
value: string;
9+
}
10+
11+
// Ciphertext
12+
interface Ciphertext {
13+
value: string;
14+
}
715

16+
describe("Paillier", function () {
817
it("should add 2 ciphertexts", async function () {
9-
const { publicKey, privateKey } = await paillierBigint.generateRandomKeys(256);
18+
const Paillier = await ethers.deployContract("Paillier");
19+
20+
const { publicKey, privateKey } =
21+
await paillierBigint.generateRandomKeys(256);
1022
const a: bigint = BigInt(1);
1123
const b: bigint = BigInt(2);
12-
const enc_a = ethers.toBeHex(publicKey.encrypt(a));
13-
const enc_b = ethers.toBeHex(publicKey.encrypt(b));
24+
const enc_a: Ciphertext = {
25+
value: ethers.toBeHex(publicKey.encrypt(a)),
26+
};
27+
const enc_b: Ciphertext = {
28+
value: ethers.toBeHex(publicKey.encrypt(b)),
29+
};
1430

1531
// Public key
16-
const pub_n = ethers.toBeHex(publicKey.n);
32+
const pub_n: PublicKey = {
33+
value: ethers.toBeHex(publicKey.n),
34+
};
1735

1836
// bit length will differ to what has been stated in this script.
19-
// if using 256-bit key, bit_length will be 264 as "0x" prefix may have been factored in
20-
21-
// Now lets deploy the contract
22-
const [owner] = await ethers.getSigners();
23-
const paillierSolidity = await ethers.deployContract("Paillier");
24-
const enc_sum = await paillierSolidity.add(enc_a, enc_b, pub_n);
37+
// if using 256-bit key, bit_length will be 264 as "0x" prefix may have been factored in
38+
// Now lets deploy the contract and test the addition
39+
const enc_sum = await Paillier.add(enc_a, enc_b, pub_n);
2540
const enc_sum_int = bigIntConversion.hexToBigint(enc_sum[0]);
2641

2742
// Conversion to int for convenience
@@ -30,44 +45,41 @@ describe("Paillier", function () {
3045

3146
// We want dec_sum to equal 3
3247
expect(dec_sum).to.equal(3);
33-
3448
});
3549

3650
it("should encrypt zero", async function () {
37-
const { publicKey, privateKey } = await paillierBigint.generateRandomKeys(256);
38-
const [owner] = await ethers.getSigners();
39-
const paillierSolidity = await ethers.deployContract("Paillier");
40-
// Arbitary random number - 10000
41-
const rand = ethers.toBeHex(Math.floor(Math.random() * 10000));
51+
const { publicKey, privateKey } =
52+
await paillierBigint.generateRandomKeys(256);
53+
const Paillier = await ethers.deployContract("Paillier");
54+
// Arbitary random number - 1000000
55+
const rand = ethers.toBeHex(Math.floor(Math.random() * 1000000));
4256

4357
// Public key
44-
const pub_n = ethers.toBeHex(publicKey.n);
45-
const enc_zero = await paillierSolidity.encryptZero(rand, pub_n);
58+
const pub_n = { value: ethers.toBeHex(publicKey.n) };
59+
const enc_zero = await Paillier.encryptZero(rand, pub_n);
4660
const enc_zero_int = bigIntConversion.hexToBigint(enc_zero[0]);
4761
const dec_zero = Number(privateKey.decrypt(enc_zero_int));
4862
expect(dec_zero).to.equal(0);
49-
5063
});
5164

5265
it("should multiply encrypted value by a scalar", async function () {
53-
const { publicKey, privateKey } = await paillierBigint.generateRandomKeys(256);
66+
const { publicKey, privateKey } =
67+
await paillierBigint.generateRandomKeys(256);
5468
const [owner] = await ethers.getSigners();
55-
const paillierSolidity = await ethers.deployContract("Paillier");
69+
const Paillier = await ethers.deployContract("Paillier");
5670
const a: bigint = BigInt(2);
5771
const b: bigint = BigInt(5);
58-
const enc_a = ethers.toBeHex(publicKey.encrypt(a));
72+
const enc_a = { value: ethers.toBeHex(publicKey.encrypt(a)) };
5973

6074
// Public key
61-
const pub_n = ethers.toBeHex(publicKey.n);
62-
const enc_scalar = await paillierSolidity.mul(enc_a, b, pub_n);
75+
const pub_n = { value: ethers.toBeHex(publicKey.n) };
76+
const enc_scalar = await Paillier.mul(enc_a, b, pub_n);
6377

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

6781
const dec_scalar = Number(privateKey.decrypt(enc_scalar_int));
6882
console.log("Decrypted Scalar:", dec_scalar);
6983
expect(dec_scalar).to.equal(10);
70-
7184
});
72-
73-
});
85+
});

0 commit comments

Comments
 (0)