Skip to content

Commit 90b6705

Browse files
Merge pull request #385 from zama-ai/feat/add-array-eq
Feat: add array equality
2 parents 8795947 + acf45d3 commit 90b6705

File tree

10 files changed

+369
-6
lines changed

10 files changed

+369
-6
lines changed

codegen/templates.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ function fheLibCustomInterfaceFunctions(): string {
143143
function trivialEncrypt(uint256 ct, bytes1 toType) external pure returns (uint256 result);
144144
function decrypt(uint256 ct) external view returns (uint256 result);
145145
function fheIfThenElse(uint256 control, uint256 ifTrue, uint256 ifFalse) external pure returns (uint256 result);
146+
function fheArrayEq(uint256[] memory lhs, uint256[] memory rhs) external pure returns (uint256 result);
146147
function fheRand(bytes1 randType) external view returns (uint256 result);
147148
function fheRandBounded(uint256 upperBound, bytes1 randType) external view returns (uint256 result);
148149
`;
@@ -226,6 +227,7 @@ library TFHE {
226227

227228
// TODO: Decide whether we want to have mixed-inputs for CMUX/Select
228229
supportedBits.forEach((bits) => res.push(tfheSelect(bits)));
230+
supportedBits.forEach((bits) => res.push(tfheEq(bits)));
229231
supportedBits.forEach((outputBits) => {
230232
supportedBits.forEach((inputBits) => {
231233
res.push(tfheAsEboolCustomCast(inputBits, outputBits));
@@ -534,6 +536,23 @@ function tfheSelect(inputBits: number): string {
534536
}`;
535537
}
536538

539+
function tfheEq(inputBits: number): string {
540+
return `
541+
function eq(euint${inputBits}[] memory a, euint${inputBits}[] memory b) internal pure returns (ebool) {
542+
require(a.length == b.length, "Both arrays are not of the same size.");
543+
uint256[] memory lhs = new uint256[](a.length);
544+
uint256[] memory rhs = new uint256[](b.length);
545+
for (uint i = 0; i < a.length; i++) {
546+
lhs[i] = euint${inputBits}.unwrap(a[i]);
547+
}
548+
for (uint i = 0; i < b.length; i++) {
549+
rhs[i] = euint${inputBits}.unwrap(b[i]);
550+
}
551+
return ebool.wrap(Impl.eq(lhs, rhs));
552+
}
553+
`;
554+
}
555+
537556
function tfheAsEboolCustomCast(inputBits: number, outputBits: number): string {
538557
if (inputBits == outputBits) {
539558
return '';
@@ -732,8 +751,6 @@ function tfheCustomMethods(ctx: CodegenContext, mocked: boolean): string {
732751
}
733752
}
734753
735-
736-
737754
// Returns the network public FHE key.
738755
function fhePubKey() internal view returns (bytes memory) {
739756
return Impl.fhePubKey();
@@ -901,6 +918,10 @@ function implCustomMethods(ctx: CodegenContext): string {
901918
result = FhevmLib(address(EXT_TFHE_LIBRARY)).fheIfThenElse(control, ifTrue, ifFalse);
902919
}
903920
921+
function eq(uint256[] memory lhs, uint256[] memory rhs) internal pure returns (uint256 result) {
922+
result = FhevmLib(address(EXT_TFHE_LIBRARY)).fheArrayEq(lhs, rhs);
923+
}
924+
904925
function reencrypt(uint256 ciphertext, bytes32 publicKey) internal view returns (bytes memory reencrypted) {
905926
return FhevmLib(address(EXT_TFHE_LIBRARY)).reencrypt(ciphertext, uint256(publicKey));
906927
}
@@ -1017,6 +1038,14 @@ library Impl {
10171038
result = (lhs == rhs) ? 1 : 0;
10181039
}
10191040
1041+
function eq(uint256[] memory lhs, uint256[] memory rhs) internal pure returns (uint256 result) {
1042+
require(lhs.length == rhs.length, "Both arrays are not of the same size.");
1043+
result = 1;
1044+
for (uint i = 0; i < lhs.length; i++) {
1045+
if (lhs[i] != rhs[i]) return;
1046+
}
1047+
}
1048+
10201049
function ne(uint256 lhs, uint256 rhs, bool /*scalar*/) internal pure returns (uint256 result) {
10211050
result = (lhs != rhs) ? 1 : 0;
10221051
}

examples/tests/TFHEManualTestSuite.sol

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,106 @@ import "../../abstracts/Reencrypt.sol";
55
import "../../lib/TFHE.sol";
66

77
contract TFHEManualTestSuite is Reencrypt {
8+
function test_eq_array_4(
9+
bytes calldata a,
10+
bytes calldata b,
11+
bytes calldata c,
12+
bytes calldata d
13+
) public view returns (bool) {
14+
euint4 aProc = TFHE.asEuint4(a);
15+
euint4 bProc = TFHE.asEuint4(b);
16+
euint4 cProc = TFHE.asEuint4(c);
17+
euint4 dProc = TFHE.asEuint4(d);
18+
euint4[] memory arrA = new euint4[](2);
19+
arrA[0] = aProc;
20+
arrA[1] = bProc;
21+
euint4[] memory arrB = new euint4[](2);
22+
arrB[0] = cProc;
23+
arrB[1] = dProc;
24+
ebool result = TFHE.eq(arrA, arrB);
25+
return TFHE.decrypt(result);
26+
}
27+
28+
function test_eq_array_8(
29+
bytes calldata a,
30+
bytes calldata b,
31+
bytes calldata c,
32+
bytes calldata d
33+
) public view returns (bool) {
34+
euint8 aProc = TFHE.asEuint8(a);
35+
euint8 bProc = TFHE.asEuint8(b);
36+
euint8 cProc = TFHE.asEuint8(c);
37+
euint8 dProc = TFHE.asEuint8(d);
38+
euint8[] memory arrA = new euint8[](2);
39+
arrA[0] = aProc;
40+
arrA[1] = bProc;
41+
euint8[] memory arrB = new euint8[](2);
42+
arrB[0] = cProc;
43+
arrB[1] = dProc;
44+
ebool result = TFHE.eq(arrA, arrB);
45+
return TFHE.decrypt(result);
46+
}
47+
48+
function test_eq_array_16(
49+
bytes calldata a,
50+
bytes calldata b,
51+
bytes calldata c,
52+
bytes calldata d
53+
) public view returns (bool) {
54+
euint16 aProc = TFHE.asEuint16(a);
55+
euint16 bProc = TFHE.asEuint16(b);
56+
euint16 cProc = TFHE.asEuint16(c);
57+
euint16 dProc = TFHE.asEuint16(d);
58+
euint16[] memory arrA = new euint16[](2);
59+
arrA[0] = aProc;
60+
arrA[1] = bProc;
61+
euint16[] memory arrB = new euint16[](2);
62+
arrB[0] = cProc;
63+
arrB[1] = dProc;
64+
ebool result = TFHE.eq(arrA, arrB);
65+
return TFHE.decrypt(result);
66+
}
67+
68+
function test_eq_array_32(
69+
bytes calldata a,
70+
bytes calldata b,
71+
bytes calldata c,
72+
bytes calldata d
73+
) public view returns (bool) {
74+
euint32 aProc = TFHE.asEuint32(a);
75+
euint32 bProc = TFHE.asEuint32(b);
76+
euint32 cProc = TFHE.asEuint32(c);
77+
euint32 dProc = TFHE.asEuint32(d);
78+
euint32[] memory arrA = new euint32[](2);
79+
arrA[0] = aProc;
80+
arrA[1] = bProc;
81+
euint32[] memory arrB = new euint32[](2);
82+
arrB[0] = cProc;
83+
arrB[1] = dProc;
84+
ebool result = TFHE.eq(arrA, arrB);
85+
return TFHE.decrypt(result);
86+
}
87+
88+
function test_eq_array_64(
89+
bytes calldata a,
90+
bytes calldata b,
91+
bytes calldata c,
92+
bytes calldata d
93+
) public view returns (bool) {
94+
euint64 aProc = TFHE.asEuint64(a);
95+
euint64 bProc = TFHE.asEuint64(b);
96+
euint64 cProc = TFHE.asEuint64(c);
97+
euint64 dProc = TFHE.asEuint64(d);
98+
euint64[] memory arrA = new euint64[](2);
99+
arrA[0] = aProc;
100+
arrA[1] = bProc;
101+
euint64[] memory arrB = new euint64[](2);
102+
arrB[0] = cProc;
103+
arrB[1] = dProc;
104+
ebool result = TFHE.eq(arrA, arrB);
105+
return TFHE.decrypt(result);
106+
}
107+
8108
function test_select(
9109
bytes calldata control,
10110
bytes calldata ifTrue,

launch-fhevm.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ ORACLE_CONTRACT_PREDEPLOY_ADDRESS=$(grep ORACLE_CONTRACT_PREDEPLOY_ADDRESS oracl
1111
docker run -d -i -p 8545:8545 --rm --name fhevm \
1212
-e PRIVATE_KEY_ORACLE_RELAYER="$PRIVATE_KEY_ORACLE_RELAYER" \
1313
-e ORACLE_CONTRACT_PREDEPLOY_ADDRESS="$ORACLE_CONTRACT_PREDEPLOY_ADDRESS" \
14-
ghcr.io/zama-ai/ethermint-dev-node:v0.4.2
14+
ghcr.io/zama-ai/ethermint-dev-node:v0.4.3-arrayeq
1515

1616
sleep 10
1717

lib/Impl.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ interface FhevmLib {
6363

6464
function fheIfThenElse(uint256 control, uint256 ifTrue, uint256 ifFalse) external pure returns (uint256 result);
6565

66+
function fheArrayEq(uint256[] memory lhs, uint256[] memory rhs) external pure returns (uint256 result);
67+
6668
function fheRand(bytes1 randType) external view returns (uint256 result);
6769

6870
function fheRandBounded(uint256 upperBound, bytes1 randType) external view returns (uint256 result);
@@ -267,6 +269,10 @@ library Impl {
267269
result = FhevmLib(address(EXT_TFHE_LIBRARY)).fheIfThenElse(control, ifTrue, ifFalse);
268270
}
269271

272+
function eq(uint256[] memory lhs, uint256[] memory rhs) internal pure returns (uint256 result) {
273+
result = FhevmLib(address(EXT_TFHE_LIBRARY)).fheArrayEq(lhs, rhs);
274+
}
275+
270276
function reencrypt(uint256 ciphertext, bytes32 publicKey) internal view returns (bytes memory reencrypted) {
271277
return FhevmLib(address(EXT_TFHE_LIBRARY)).reencrypt(ciphertext, uint256(publicKey));
272278
}

lib/TFHE.sol

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5262,6 +5262,71 @@ library TFHE {
52625262
return euint64.wrap(Impl.select(ebool.unwrap(control), euint64.unwrap(a), euint64.unwrap(b)));
52635263
}
52645264

5265+
function eq(euint4[] memory a, euint4[] memory b) internal pure returns (ebool) {
5266+
require(a.length == b.length, "Both arrays are not of the same size.");
5267+
uint256[] memory lhs = new uint256[](a.length);
5268+
uint256[] memory rhs = new uint256[](b.length);
5269+
for (uint i = 0; i < a.length; i++) {
5270+
lhs[i] = euint4.unwrap(a[i]);
5271+
}
5272+
for (uint i = 0; i < b.length; i++) {
5273+
rhs[i] = euint4.unwrap(b[i]);
5274+
}
5275+
return ebool.wrap(Impl.eq(lhs, rhs));
5276+
}
5277+
5278+
function eq(euint8[] memory a, euint8[] memory b) internal pure returns (ebool) {
5279+
require(a.length == b.length, "Both arrays are not of the same size.");
5280+
uint256[] memory lhs = new uint256[](a.length);
5281+
uint256[] memory rhs = new uint256[](b.length);
5282+
for (uint i = 0; i < a.length; i++) {
5283+
lhs[i] = euint8.unwrap(a[i]);
5284+
}
5285+
for (uint i = 0; i < b.length; i++) {
5286+
rhs[i] = euint8.unwrap(b[i]);
5287+
}
5288+
return ebool.wrap(Impl.eq(lhs, rhs));
5289+
}
5290+
5291+
function eq(euint16[] memory a, euint16[] memory b) internal pure returns (ebool) {
5292+
require(a.length == b.length, "Both arrays are not of the same size.");
5293+
uint256[] memory lhs = new uint256[](a.length);
5294+
uint256[] memory rhs = new uint256[](b.length);
5295+
for (uint i = 0; i < a.length; i++) {
5296+
lhs[i] = euint16.unwrap(a[i]);
5297+
}
5298+
for (uint i = 0; i < b.length; i++) {
5299+
rhs[i] = euint16.unwrap(b[i]);
5300+
}
5301+
return ebool.wrap(Impl.eq(lhs, rhs));
5302+
}
5303+
5304+
function eq(euint32[] memory a, euint32[] memory b) internal pure returns (ebool) {
5305+
require(a.length == b.length, "Both arrays are not of the same size.");
5306+
uint256[] memory lhs = new uint256[](a.length);
5307+
uint256[] memory rhs = new uint256[](b.length);
5308+
for (uint i = 0; i < a.length; i++) {
5309+
lhs[i] = euint32.unwrap(a[i]);
5310+
}
5311+
for (uint i = 0; i < b.length; i++) {
5312+
rhs[i] = euint32.unwrap(b[i]);
5313+
}
5314+
return ebool.wrap(Impl.eq(lhs, rhs));
5315+
}
5316+
5317+
function eq(euint64[] memory a, euint64[] memory b) internal pure returns (ebool) {
5318+
require(a.length == b.length, "Both arrays are not of the same size.");
5319+
uint256[] memory lhs = new uint256[](a.length);
5320+
uint256[] memory rhs = new uint256[](b.length);
5321+
for (uint i = 0; i < a.length; i++) {
5322+
lhs[i] = euint64.unwrap(a[i]);
5323+
}
5324+
for (uint i = 0; i < b.length; i++) {
5325+
rhs[i] = euint64.unwrap(b[i]);
5326+
}
5327+
return ebool.wrap(Impl.eq(lhs, rhs));
5328+
}
5329+
52655330
// Cast an encrypted integer from euint8 to euint4.
52665331
function asEuint4(euint8 value) internal pure returns (euint4) {
52675332
return euint4.wrap(Impl.cast(euint8.unwrap(value), Common.euint4_t));

mocks/Impl.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ library Impl {
6565
result = (lhs == rhs) ? 1 : 0;
6666
}
6767

68+
function eq(uint256[] memory lhs, uint256[] memory rhs) internal pure returns (uint256 result) {
69+
require(lhs.length == rhs.length, "Both arrays are not of the same size.");
70+
result = 1;
71+
for (uint i = 0; i < lhs.length; i++) {
72+
if (lhs[i] != rhs[i]) return;
73+
}
74+
}
75+
6876
function ne(uint256 lhs, uint256 rhs, bool /*scalar*/) internal pure returns (uint256 result) {
6977
result = (lhs != rhs) ? 1 : 0;
7078
}

mocks/TFHE.sol

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5262,6 +5262,71 @@ library TFHE {
52625262
return euint64.wrap(Impl.select(ebool.unwrap(control), euint64.unwrap(a), euint64.unwrap(b)));
52635263
}
52645264

5265+
function eq(euint4[] memory a, euint4[] memory b) internal pure returns (ebool) {
5266+
require(a.length == b.length, "Both arrays are not of the same size.");
5267+
uint256[] memory lhs = new uint256[](a.length);
5268+
uint256[] memory rhs = new uint256[](b.length);
5269+
for (uint i = 0; i < a.length; i++) {
5270+
lhs[i] = euint4.unwrap(a[i]);
5271+
}
5272+
for (uint i = 0; i < b.length; i++) {
5273+
rhs[i] = euint4.unwrap(b[i]);
5274+
}
5275+
return ebool.wrap(Impl.eq(lhs, rhs));
5276+
}
5277+
5278+
function eq(euint8[] memory a, euint8[] memory b) internal pure returns (ebool) {
5279+
require(a.length == b.length, "Both arrays are not of the same size.");
5280+
uint256[] memory lhs = new uint256[](a.length);
5281+
uint256[] memory rhs = new uint256[](b.length);
5282+
for (uint i = 0; i < a.length; i++) {
5283+
lhs[i] = euint8.unwrap(a[i]);
5284+
}
5285+
for (uint i = 0; i < b.length; i++) {
5286+
rhs[i] = euint8.unwrap(b[i]);
5287+
}
5288+
return ebool.wrap(Impl.eq(lhs, rhs));
5289+
}
5290+
5291+
function eq(euint16[] memory a, euint16[] memory b) internal pure returns (ebool) {
5292+
require(a.length == b.length, "Both arrays are not of the same size.");
5293+
uint256[] memory lhs = new uint256[](a.length);
5294+
uint256[] memory rhs = new uint256[](b.length);
5295+
for (uint i = 0; i < a.length; i++) {
5296+
lhs[i] = euint16.unwrap(a[i]);
5297+
}
5298+
for (uint i = 0; i < b.length; i++) {
5299+
rhs[i] = euint16.unwrap(b[i]);
5300+
}
5301+
return ebool.wrap(Impl.eq(lhs, rhs));
5302+
}
5303+
5304+
function eq(euint32[] memory a, euint32[] memory b) internal pure returns (ebool) {
5305+
require(a.length == b.length, "Both arrays are not of the same size.");
5306+
uint256[] memory lhs = new uint256[](a.length);
5307+
uint256[] memory rhs = new uint256[](b.length);
5308+
for (uint i = 0; i < a.length; i++) {
5309+
lhs[i] = euint32.unwrap(a[i]);
5310+
}
5311+
for (uint i = 0; i < b.length; i++) {
5312+
rhs[i] = euint32.unwrap(b[i]);
5313+
}
5314+
return ebool.wrap(Impl.eq(lhs, rhs));
5315+
}
5316+
5317+
function eq(euint64[] memory a, euint64[] memory b) internal pure returns (ebool) {
5318+
require(a.length == b.length, "Both arrays are not of the same size.");
5319+
uint256[] memory lhs = new uint256[](a.length);
5320+
uint256[] memory rhs = new uint256[](b.length);
5321+
for (uint i = 0; i < a.length; i++) {
5322+
lhs[i] = euint64.unwrap(a[i]);
5323+
}
5324+
for (uint i = 0; i < b.length; i++) {
5325+
rhs[i] = euint64.unwrap(b[i]);
5326+
}
5327+
return ebool.wrap(Impl.eq(lhs, rhs));
5328+
}
5329+
52655330
// Cast an encrypted integer from euint8 to euint4.
52665331
function asEuint4(euint8 value) internal pure returns (euint4) {
52675332
return euint4.wrap(Impl.cast(euint8.unwrap(value), Common.euint4_t));

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "fhevm",
33
"description": "A Solidity library for interacting with the Zama Blockchain",
4-
"version": "0.4.0",
4+
"version": "0.4.1",
55
"main": "lib/TFHE.sol",
66
"directories": {
77
"example": "examples",

0 commit comments

Comments
 (0)