Skip to content

Commit 569a16b

Browse files
committed
feat: added example ERC20 contract
1 parent 379fedc commit 569a16b

File tree

6 files changed

+551
-129
lines changed

6 files changed

+551
-129
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ To use this smart contract, you need to deploy it on the Ethereum blockchain. Yo
2323

2424
# Use Cases
2525

26-
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:
26+
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.
27+
28+
Included is a comprehensive [DiscreteERC20](contracts/examples/DiscreteERC20.sol) contract, demonstrating the library's homomorphic properties with an Ethereum Token, preserving transaction privacy onchain.
29+
30+
Here are five possible applications with corresponding example Solidity code snippets:
2731

2832
## Private Voting System
2933

contracts/Paillier.sol

+40-1
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,52 @@ contract Paillier {
209209
return enc_result;
210210
}
211211

212+
function div_const(
213+
Ciphertext calldata a,
214+
uint256 b,
215+
PublicKey calldata publicKey
216+
) public view returns (BigNumber memory) {
217+
BigNumber memory enc_value = BigNumber(
218+
a.value,
219+
false,
220+
BigNum.bitLength(a.value)
221+
);
222+
BigNumber memory pub_n = BigNumber(
223+
publicKey.n,
224+
true,
225+
BigNum.bitLength(publicKey.n)
226+
);
227+
228+
BigNumber memory bb = BigNumber(abi.encodePacked(b), false, 256);
229+
BigNumber memory bi = BigNumber(abi.encodePacked(b), false, 256);
230+
231+
BigNumber memory modulus = BigNum.pow(pub_n, 2);
232+
233+
// BigNumber memory inverse = BigNum.mod(BigNum.mod(bb, pub_n), modulus);
234+
235+
// Fermat's Little Theorem: a^(p-1) ≡ 1 (mod p)
236+
// Therefore, a^(p-2) is the modular inverse of a mod p
237+
//return pow(a, p - 2, p)
238+
// ciphertext.modPow(plaintext, public_key.modulus);
239+
BigNumber memory inverse = BigNum.modexp(bb, bi, pub_n, modulus);
240+
241+
// Calculate the encrypted result as enc_value^b % pub_n^2
242+
BigNumber memory enc_result = BigNum.modexp(
243+
enc_value,
244+
inverse,
245+
modulus
246+
);
247+
248+
return enc_result;
249+
}
250+
212251
/// @notice Encrypts zero using a random value and a public key
213252
/// @dev The encryption is performed as r^n % n^2, where r is the random value
214253
/// @param rnd The random value in bytes
215254
/// @param publicKey The public key in bytes
216255
/// @return enc_zero The encrypted zero as a BigNumber
217256
function encryptZero(
218-
bytes calldata rnd,
257+
bytes memory rnd,
219258
PublicKey calldata publicKey
220259
) public view returns (BigNumber memory) {
221260
// Create BigNumber representations for the random value and the public key

contracts/examples/DiscreteERC20.sol

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import "../Paillier.sol";
5+
6+
/// @title DiscreteERC20: An ERC20 token contract with encrypted balances using the Paillier cryptosystem
7+
/// @dev This contract demonstrates an example of an ERC20 token where balances are encrypted to preserve user privacy.
8+
contract DiscreteERC20 {
9+
/// @dev Instance of the Paillier contract for cryptographic operations
10+
Paillier private paillier;
11+
12+
/// @dev Public key structure used for the Paillier encryption
13+
PublicKey public publicKey;
14+
15+
/// @notice An event emitted when a transfer of tokens is made
16+
/// @param from Address sending the tokens
17+
/// @param to Address receiving the tokens
18+
/// @param value Amount of tokens transferred, represented as encrypted data
19+
event Transfer(address indexed from, address indexed to, Ciphertext value);
20+
21+
/// @notice An event emitted when a spender is approved to spend tokens
22+
/// @param owner Address owning the tokens
23+
/// @param spender Address authorized to spend the tokens
24+
/// @param value Amount of tokens approved, represented as encrypted data
25+
event Approval(
26+
address indexed owner,
27+
address indexed spender,
28+
Ciphertext value
29+
);
30+
31+
/// @notice An event emitted when a balance request is initiated
32+
/// @param account Address whose balance is being requested
33+
event RequestBalance(address indexed account);
34+
35+
/// @notice An event emitted in response to a balance request
36+
/// @param account Address whose balance was requested
37+
/// @param balance The balance of the account (unencrypted for event)
38+
event ResponseBalance(address indexed account, uint balance);
39+
40+
/// @dev The total supply of tokens, encrypted
41+
Ciphertext public totalSupply;
42+
43+
/// @dev Mapping of encrypted balances per address
44+
mapping(address => Ciphertext) public balanceOf;
45+
46+
/// @dev Nested mapping to manage encrypted allowances
47+
mapping(address => mapping(address => Ciphertext)) public allowance;
48+
49+
/// @notice The name of the token
50+
string public name;
51+
52+
/// @notice The symbol of the token
53+
string public symbol;
54+
55+
/// @notice The number of decimals the token uses
56+
uint8 public decimals;
57+
58+
/// @notice Constructor to set initial token details and cryptographic components
59+
/// @param _name Name of the token
60+
/// @param _symbol Symbol of the token
61+
/// @param _decimals Number of decimal places the token uses
62+
/// @param _paillier Address of the Paillier contract
63+
/// @param _publicKey Public key for the Paillier encryption
64+
constructor(
65+
string memory _name,
66+
string memory _symbol,
67+
uint8 _decimals,
68+
address _paillier,
69+
PublicKey memory _publicKey
70+
) {
71+
name = _name;
72+
symbol = _symbol;
73+
decimals = _decimals;
74+
paillier = Paillier(_paillier);
75+
publicKey = _publicKey;
76+
totalSupply = _zero();
77+
}
78+
79+
/// @notice Emits an event to request the balance of the sender
80+
function requestBalance() external {
81+
emit RequestBalance(msg.sender);
82+
}
83+
84+
/// @notice Emits an event with the response balance for a specified address
85+
/// @param target The address whose balance is being reported
86+
/// @param balance The balance of the specified address
87+
function responseBalance(address target, uint balance) external {
88+
emit ResponseBalance(target, balance);
89+
}
90+
91+
/// @notice Transfers encrypted tokens to a specified address
92+
/// @param recipient The address to receive the tokens
93+
/// @param amount The amount of tokens to transfer, represented as encrypted data
94+
/// @return success A boolean value indicating success of the transfer
95+
function transfer(
96+
address recipient,
97+
Ciphertext calldata amount
98+
) external returns (bool success) {
99+
require(
100+
keccak256(abi.encodePacked(balanceOf[msg.sender].value)) !=
101+
keccak256(bytes("")),
102+
"DiscreteERC20: transfer from the zero address"
103+
);
104+
105+
if (
106+
keccak256(abi.encodePacked(balanceOf[recipient].value)) ==
107+
keccak256(bytes(""))
108+
) {
109+
balanceOf[recipient] = amount;
110+
} else {
111+
balanceOf[recipient] = this._add(balanceOf[recipient], amount);
112+
}
113+
balanceOf[msg.sender] = this._sub(balanceOf[msg.sender], amount);
114+
emit Transfer(msg.sender, recipient, amount);
115+
return true;
116+
}
117+
118+
/// @notice Approves a spender to use a specified amount of the owner's tokens
119+
/// @param spender The address authorized to spend the tokens
120+
/// @param amount The amount of tokens they are authorized to use, represented as encrypted data
121+
/// @return success A boolean value indicating success of the approval
122+
function approve(
123+
address spender,
124+
Ciphertext calldata amount
125+
) external returns (bool success) {
126+
allowance[msg.sender][spender] = amount;
127+
emit Approval(msg.sender, spender, amount);
128+
return true;
129+
}
130+
131+
/// @notice Transfers tokens from one address to another using an allowance
132+
/// @param sender The address from which tokens are transferred
133+
/// @param recipient The address to which tokens are transferred
134+
/// @param amount The amount of tokens to transfer, represented as encrypted data
135+
/// @return success A boolean value indicating success of the transfer
136+
function transferFrom(
137+
address sender,
138+
address recipient,
139+
Ciphertext calldata amount
140+
) external returns (bool success) {
141+
allowance[sender][msg.sender] = this._sub(
142+
allowance[sender][msg.sender],
143+
amount
144+
);
145+
balanceOf[sender] = this._sub(balanceOf[sender], amount);
146+
balanceOf[recipient] = this._add(balanceOf[recipient], amount);
147+
emit Transfer(sender, recipient, amount);
148+
return true;
149+
}
150+
151+
/// @dev Internal function to mint new encrypted tokens
152+
/// @param to The address to receive the newly minted tokens
153+
/// @param amount The amount of tokens to mint, represented as encrypted data
154+
function _mint(address to, Ciphertext calldata amount) internal {
155+
if (
156+
keccak256(abi.encodePacked(balanceOf[to].value)) ==
157+
keccak256(bytes(""))
158+
) {
159+
balanceOf[to] = amount;
160+
} else {
161+
balanceOf[to] = this._add(balanceOf[to], amount);
162+
}
163+
totalSupply = this._add(totalSupply, amount);
164+
emit Transfer(address(0), to, amount);
165+
}
166+
167+
/// @dev Internal function to burn encrypted tokens
168+
/// @param from The address from which tokens are burned
169+
/// @param amount The amount of tokens to burn, represented as encrypted data
170+
function _burn(address from, Ciphertext calldata amount) internal {
171+
balanceOf[from] = this._sub(balanceOf[from], amount);
172+
totalSupply = this._sub(totalSupply, amount);
173+
emit Transfer(from, address(0), amount);
174+
}
175+
176+
/// @notice External function to mint new encrypted tokens
177+
/// @param to The address to receive the newly minted tokens
178+
/// @param amount The amount of tokens to mint, represented as encrypted data
179+
function mint(address to, Ciphertext calldata amount) external {
180+
_mint(to, amount);
181+
}
182+
183+
/// @notice External function to burn encrypted tokens
184+
/// @param from The address from which tokens are burned
185+
/// @param amount The amount of tokens to burn, represented as encrypted data
186+
function burn(address from, Ciphertext calldata amount) external {
187+
_burn(from, amount);
188+
}
189+
190+
/// @dev Internal function to generate an encrypted zero value using randomness
191+
/// @return A Ciphertext structure representing an encrypted value of zero
192+
function _zero() public view returns (Ciphertext memory) {
193+
bytes memory rnd = abi.encodePacked(
194+
block.timestamp,
195+
blockhash(block.number - 1)
196+
);
197+
return Ciphertext(paillier.encryptZero(rnd, publicKey).val);
198+
}
199+
200+
/// @dev Internal function to add two encrypted values
201+
/// @param a The first encrypted value
202+
/// @param b The second encrypted value
203+
/// @return The result of the addition, represented as encrypted data
204+
function _add(
205+
Ciphertext calldata a,
206+
Ciphertext calldata b
207+
) public view returns (Ciphertext memory) {
208+
return Ciphertext(paillier.add(a, b, publicKey).val);
209+
}
210+
211+
/// @dev Internal function to subtract one encrypted value from another
212+
/// @param a The encrypted value to subtract from
213+
/// @param b The encrypted value to subtract
214+
/// @return The result of the subtraction, represented as encrypted data
215+
function _sub(
216+
Ciphertext calldata a,
217+
Ciphertext calldata b
218+
) public view returns (Ciphertext memory) {
219+
return Ciphertext(paillier.sub(a, b, publicKey).val);
220+
}
221+
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@
2424
"bigint-conversion": "^2.4.3",
2525
"paillier-bigint": "^3.4.3"
2626
}
27-
}
27+
}

0 commit comments

Comments
 (0)