Skip to content

Commit

Permalink
Merge pull request #546 from zama-ai/fhevmjsMockedInput
Browse files Browse the repository at this point in the history
feat: use dynamic lists in fhevmjsMocked
  • Loading branch information
jatZama authored Oct 3, 2024
2 parents b6ce593 + b737b6d commit c97075b
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 67 deletions.
122 changes: 79 additions & 43 deletions test/fhevmjsMocked.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { toBigIntLE } from 'bigint-buffer';
import { toBigIntBE } from 'bigint-buffer';
import { toBufferBE } from 'bigint-buffer';
import crypto from 'crypto';
import dotenv from 'dotenv';
Expand Down Expand Up @@ -30,12 +30,14 @@ enum Types {
ebytes256,
}

const sum = (arr: number[]) => arr.reduce((acc, val) => acc + val, 0);

function bytesToBigInt(byteArray: Uint8Array): bigint {
if (!byteArray || byteArray?.length === 0) {
return BigInt(0);
}
const buffer = Buffer.from(byteArray);
const result = toBigIntLE(buffer);
const result = toBigIntBE(buffer);
return result;
}

Expand All @@ -50,37 +52,52 @@ function createUintToUint8ArrayFunction(numBits: number) {

let byteBuffer;
let totalBuffer;
const padBuffer = numBytes <= 20 ? Buffer.alloc(20 - numBytes) : Buffer.alloc(0); // to fit it in an E160List

switch (numBits) {
case 1:
case 2: // ebool takes 2 bits
byteBuffer = Buffer.from([Types.ebool]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer, padBuffer]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 4:
byteBuffer = Buffer.from([Types.euint4]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer, padBuffer]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 8:
byteBuffer = Buffer.from([Types.euint8]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer, padBuffer]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 16:
byteBuffer = Buffer.from([Types.euint16]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer, padBuffer]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 32:
byteBuffer = Buffer.from([Types.euint32]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer, padBuffer]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 64:
byteBuffer = Buffer.from([Types.euint64]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer, padBuffer]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 128:
byteBuffer = Buffer.from([Types.euint128]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 160:
byteBuffer = Buffer.from([Types.eaddress]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 256:
byteBuffer = Buffer.from([Types.euint256]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 512:
byteBuffer = Buffer.from([Types.ebytes64]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 1024:
byteBuffer = Buffer.from([Types.ebytes128]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
break;
case 2048:
byteBuffer = Buffer.from([Types.ebytes256]);
totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]);
Expand Down Expand Up @@ -153,43 +170,57 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress:
if ((typeof value !== 'bigint' || typeof value !== 'number') && Number(value) > 1)
throw new Error('The value must be 1 or 0.');
values.push(BigInt(value));
bits.push(1);
bits.push(2); // ebool takes 2 bits instead of one: only exception in TFHE-rs
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
add4(value: number | bigint) {
checkEncryptedValue(value, 4);
values.push(BigInt(value));
bits.push(4);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
add8(value: number | bigint) {
checkEncryptedValue(value, 8);
values.push(BigInt(value));
bits.push(8);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
add16(value: number | bigint) {
checkEncryptedValue(value, 16);
values.push(BigInt(value));
bits.push(16);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
add32(value: number | bigint) {
checkEncryptedValue(value, 32);
values.push(BigInt(value));
bits.push(32);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
add64(value: number | bigint) {
checkEncryptedValue(value, 64);
values.push(BigInt(value));
bits.push(64);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
add128(value: number | bigint) {
checkEncryptedValue(value, 128);
values.push(BigInt(value));
bits.push(128);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
addAddress(value: string) {
Expand All @@ -198,13 +229,46 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress:
}
values.push(BigInt(value));
bits.push(160);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
add256(value: number | bigint) {
checkEncryptedValue(value, 256);
values.push(BigInt(value));
bits.push(256);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
addBytes64(value: Uint8Array) {
if (value.length !== 64) throw Error('Uncorrect length of input Uint8Array, should be 64 for an ebytes64');
const bigIntValue = bytesToBigInt(value);
checkEncryptedValue(bigIntValue, 512);
values.push(bigIntValue);
bits.push(512);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
addBytes128(value: Uint8Array) {
if (value.length !== 128) throw Error('Uncorrect length of input Uint8Array, should be 128 for an ebytes128');
const bigIntValue = bytesToBigInt(value);
checkEncryptedValue(bigIntValue, 1024);
values.push(bigIntValue);
bits.push(1024);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
addBytes256(value: Uint8Array) {
if (value.length !== 256) throw Error('Uncorrect length of input Uint8Array, should be 256 for an ebytes256');
const bigIntValue = bytesToBigInt(value);
checkEncryptedValue(bigIntValue, 2048);
values.push(bigIntValue);
bits.push(2048);
if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported');
if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported');
return this;
},
getValues() {
Expand All @@ -219,22 +283,11 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress:
return this;
},
async encrypt() {
const listType = getListType(bits);

let encrypted = Buffer.alloc(0);

switch (listType) {
case 160: {
bits.map((v, i) => {
encrypted = Buffer.concat([encrypted, createUintToUint8ArrayFunction(v)(values[i])]);
});
break;
}
case 2048: {
encrypted = createUintToUint8ArrayFunction(2048)(values[0]);
break;
}
}
bits.map((v, i) => {
encrypted = Buffer.concat([encrypted, createUintToUint8ArrayFunction(v)(values[i])]);
});

const encryptedArray = new Uint8Array(encrypted);
const hash = new Keccak(256).update(Buffer.from(encryptedArray)).digest();
Expand Down Expand Up @@ -301,7 +354,7 @@ const checkEncryptedValue = (value: number | bigint, bits: number) => {
};

export const ENCRYPTION_TYPES = {
1: 0,
2: 0, // ebool takes 2 bits
4: 1,
8: 2,
16: 3,
Expand All @@ -315,23 +368,6 @@ export const ENCRYPTION_TYPES = {
2048: 11,
};

const getListType = (bits: (keyof typeof ENCRYPTION_TYPES)[]) => {
// We limit to 12 items because for now we are using FheUint160List
if (bits.length > 12) {
throw new Error("You can't pack more than 12 values.");
}

if (bits.reduce((total, v) => total + v, 0) > 2048) {
throw new Error('Too many bits in provided values. Maximum is 2048.');
}

if (bits.some((v) => v === 2048)) {
return 2048;
} else {
return 160;
}
};

async function computeInputSignatureCopro(
hash: string,
handlesList: bigint[],
Expand Down
79 changes: 79 additions & 0 deletions test/fhevmjsTest/fhevmjsTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { expect } from 'chai';

import { createInstances } from '../instance';
import { getSigners, initSigners } from '../signers';
import { bigIntToBytes64, bigIntToBytes128, bigIntToBytes256 } from '../utils';

describe('Testing fhevmjs/fhevmjsMocked', function () {
before(async function () {
await initSigners(1);
this.signers = await getSigners();
this.contractAddress = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
this.instances = await createInstances(this.signers);
});

it('should be able to pack up to 256 ebools', async function () {
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
for (let i = 0; i < 256; i++) {
input.addBool(false);
}
await input.encrypt();
});

it('should be unable to pack more than 256 ebools', async function () {
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
for (let i = 0; i < 256; i++) {
input.addBool(true);
}
expect(() => input.addBool(false)).to.throw(
'Packing more than 256 variables in a single input ciphertext is unsupported',
);
});

it('should be able to pack up to 32 euint64s', async function () {
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
for (let i = 0; i < 32; i++) {
input.add64(1024n);
}
await input.encrypt();
});

it('should be unable to pack more than 32 euint64s', async function () {
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
for (let i = 0; i < 32; i++) {
input.add64(37n);
}
expect(() => input.add64(1n)).to.throw('Packing more than 2048 bits in a single input ciphertext is unsupported');
});

it('should be able to pack up to 2 euint128s', async function () {
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
for (let i = 0; i < 2; i++) {
input.addBytes128(bigIntToBytes128(797979n));
}
await input.encrypt();
});

it('should be unable to pack more than 2 euint128s', async function () {
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
for (let i = 0; i < 2; i++) {
input.addBytes128(bigIntToBytes128(797979n));
}
expect(() => input.addBool(false)).to.throw(
'Packing more than 2048 bits in a single input ciphertext is unsupported',
);
});

it('should be able to pack up to 2048 bits but not more', async function () {
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
input.addBytes128(bigIntToBytes128(797979n));
input.addBytes64(bigIntToBytes64(797979n));
input.add256(6887n);
input.add128(6887n);
input.add64(6887n);
input.add64(6887n);
expect(() => input.addBool(false)).to.throw(
'Packing more than 2048 bits in a single input ciphertext is unsupported',
);
});
});
14 changes: 7 additions & 7 deletions test/gatewayDecrypt/testAsyncDecrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ethers, network } from 'hardhat';
import { asyncDecrypt, awaitAllDecryptionResults } from '../asyncDecrypt';
import { createInstances } from '../instance';
import { getSigners, initSigners } from '../signers';
import { bigIntToBytes, waitNBlocks } from '../utils';
import { bigIntToBytes256, waitNBlocks } from '../utils';

describe('TestAsyncDecrypt', function () {
before(async function () {
Expand Down Expand Up @@ -324,7 +324,7 @@ describe('TestAsyncDecrypt', function () {

it('test async decrypt ebytes256 non-trivial', async function () {
const inputAlice = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
inputAlice.addBytes256(bigIntToBytes(18446744073709550022n));
inputAlice.addBytes256(bigIntToBytes256(18446744073709550022n));
const encryptedAmount = await inputAlice.encrypt();
const tx = await this.contract.requestEbytes256NonTrivial(encryptedAmount.handles[0], encryptedAmount.inputProof, {
gasLimit: 5_000_000,
Expand All @@ -339,7 +339,7 @@ describe('TestAsyncDecrypt', function () {
if (network.name === 'hardhat') {
this.snapshotId = await ethers.provider.send('evm_snapshot');
const inputAlice = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
inputAlice.addBytes256(bigIntToBytes(18446744073709550022n));
inputAlice.addBytes256(bigIntToBytes256(18446744073709550022n));
const encryptedAmount = await inputAlice.encrypt();
const tx = await this.contract.requestEbytes256NonTrivial(
encryptedAmount.handles[0],
Expand All @@ -353,7 +353,7 @@ describe('TestAsyncDecrypt', function () {

await ethers.provider.send('evm_revert', [this.snapshotId]);
const inputAlice2 = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
inputAlice2.addBytes256(bigIntToBytes(424242n));
inputAlice2.addBytes256(bigIntToBytes256(424242n));
const encryptedAmount2 = await inputAlice2.encrypt();
const tx2 = await this.contract.requestEbytes256NonTrivial(
encryptedAmount2.handles[0],
Expand All @@ -369,7 +369,7 @@ describe('TestAsyncDecrypt', function () {

it('test async decrypt mixed with ebytes256', async function () {
const inputAlice = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
inputAlice.addBytes256(bigIntToBytes(18446744073709550032n));
inputAlice.addBytes256(bigIntToBytes256(18446744073709550032n));
const encryptedAmount = await inputAlice.encrypt();
const tx = await this.contract.requestMixedBytes256(encryptedAmount.handles[0], encryptedAmount.inputProof, {
gasLimit: 5_000_000,
Expand All @@ -391,7 +391,7 @@ describe('TestAsyncDecrypt', function () {
await contract2.getAddress(),
this.signers.alice.address,
);
inputAlice.addBytes256(bigIntToBytes(18446744073709550022n));
inputAlice.addBytes256(bigIntToBytes256(18446744073709550022n));
const encryptedAmount = await inputAlice.encrypt();
const tx = await contract2.requestEbytes256NonTrivialTrustless(
encryptedAmount.handles[0],
Expand All @@ -411,7 +411,7 @@ describe('TestAsyncDecrypt', function () {
await contract2.getAddress(),
this.signers.alice.address,
);
inputAlice.addBytes256(bigIntToBytes(18446744073709550032n));
inputAlice.addBytes256(bigIntToBytes256(18446744073709550032n));
const encryptedAmount = await inputAlice.encrypt();
const tx = await contract2.requestMixedBytes256Trustless(encryptedAmount.handles[0], encryptedAmount.inputProof, {
gasLimit: 5_000_000,
Expand Down
Loading

0 comments on commit c97075b

Please sign in to comment.