From b45babbd7312243f676d421436029854e0bfb2c5 Mon Sep 17 00:00:00 2001
From: Petar Ivanov <29689712+dartdart26@users.noreply.github.com>
Date: Tue, 11 Jun 2024 13:27:35 +0300
Subject: [PATCH] feat: add TFHE decryption utils
---
README.md | 3 +-
package.json | 2 +-
src/node.ts | 1 +
src/sdk/decrypt.test.ts | 8 ++--
src/sdk/decrypt.ts | 16 +++----
src/sdk/index.ts | 9 ++++
src/utils.test.ts | 97 ++++++++++++++++++++++++++++++++++++++++-
src/utils.ts | 37 ++++++++++++++++
8 files changed, 158 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 86027a6..5bd75fe 100644
--- a/README.md
+++ b/README.md
@@ -40,13 +40,14 @@ There are two ways to contribute to the Zama fhEVM:
- you can open issues to report bugs or typos, or to suggest new ideas
- you can ask to become an official contributor by emailing hello@zama.ai. (becoming an approved contributor involves signing our Contributor License Agreement (CLA))
-Only approved contributors can send pull requests, so please make sure to get in touch before you do!
+ Only approved contributors can send pull requests, so please make sure to get in touch before you do!
## Credits
This library uses several dependencies and we would like to thank the contributors of those libraries.
## Need support?
+
diff --git a/package.json b/package.json
index c6494cc..4fee09a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fhevmjs",
- "version": "0.5.0-0",
+ "version": "0.5.0-1",
"description": "fhEVM SDK for blockchain using TFHE",
"main": "lib/node.cjs",
"types": "lib/node.d.ts",
diff --git a/src/node.ts b/src/node.ts
index 5703fbf..9d5f06e 100644
--- a/src/node.ts
+++ b/src/node.ts
@@ -1,3 +1,4 @@
//@ts-check
export * from './sdk';
export * from './tfhe';
+export { clientKeyDecryptor } from './utils';
diff --git a/src/sdk/decrypt.test.ts b/src/sdk/decrypt.test.ts
index d4d1034..4c4c58f 100644
--- a/src/sdk/decrypt.test.ts
+++ b/src/sdk/decrypt.test.ts
@@ -47,9 +47,9 @@ describe('decrypt', () => {
it('decrypts an address Uint8Array value bigger than 160 bits', async () => {
const keypair = sodium.crypto_box_keypair();
- const address = '0x9b8a8ba1f109551bd432803012645ac136ddd64dba72'
+ const address = '0x9b8a8ba1f109551bd432803012645ac136ddd64dba72';
// Must truncate to 40-digit
- const expected = '0x8ba1f109551bd432803012645ac136ddd64dba72'
+ const expected = '0x8ba1f109551bd432803012645ac136ddd64dba72';
const value = BigInt(address);
const ciphertext = sodium.crypto_box_seal(
bigIntToBytes(value),
@@ -61,9 +61,9 @@ describe('decrypt', () => {
it('decrypts an address Uint8Array value lower than 160 bits', async () => {
const keypair = sodium.crypto_box_keypair();
- const address = '0x8ba1f109551bd432803012645ac136ddd64d'
+ const address = '0x8ba1f109551bd432803012645ac136ddd64d';
// Must add padding until to 40-digit
- const expected = '0x00008ba1f109551bd432803012645ac136ddd64d'
+ const expected = '0x00008ba1f109551bd432803012645ac136ddd64d';
const value = BigInt(address);
const ciphertext = sodium.crypto_box_seal(
bigIntToBytes(value),
diff --git a/src/sdk/decrypt.ts b/src/sdk/decrypt.ts
index 3ca24b5..46572c1 100644
--- a/src/sdk/decrypt.ts
+++ b/src/sdk/decrypt.ts
@@ -29,13 +29,13 @@ export const decryptAddress = (
keypair.privateKey,
);
-let hexString = bytesToHex(decrypted);
-// Ensure hexString forms a valid 40-digit Ethereum address.
-// Truncate or pad with leading zeros as necessary to correct length issues.
-if (hexString.length > 40) {
- hexString = hexString.substring(hexString.length - 40);
-} else {
- hexString = hexString.slice(2).padStart(40, '0');
-}
+ let hexString = bytesToHex(decrypted);
+ // Ensure hexString forms a valid 40-digit Ethereum address.
+ // Truncate or pad with leading zeros as necessary to correct length issues.
+ if (hexString.length > 40) {
+ hexString = hexString.substring(hexString.length - 40);
+ } else {
+ hexString = hexString.slice(2).padStart(40, '0');
+ }
return getAddress(hexString);
};
diff --git a/src/sdk/index.ts b/src/sdk/index.ts
index feb8405..a992a84 100644
--- a/src/sdk/index.ts
+++ b/src/sdk/index.ts
@@ -68,6 +68,15 @@ export const getPublicKeyCallParams = () => ({
data: '0xd9d47bb001',
});
+export const getCiphertextCallParams = (handle: bigint) => {
+ let hex = handle.toString(16);
+ hex = hex.padStart(64, '0');
+ return {
+ to: '0x000000000000000000000000000000000000005d',
+ data: '0xff627e77' + hex,
+ };
+};
+
export const createInstance = async (
params: FhevmInstanceParams,
): Promise => {
diff --git a/src/utils.test.ts b/src/utils.test.ts
index a9c89a6..95ed0a0 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -1,6 +1,31 @@
-import { bigIntToBytes, bytesToBigInt } from './utils';
+import {
+ bigIntToBytes,
+ bytesToBigInt,
+ clientKeyDecryptor,
+ toHexString,
+} from './utils';
+import { createTfheKeypair } from './tfhe';
+import {
+ FheBool,
+ FheUint4,
+ FheUint8,
+ FheUint16,
+ FheUint32,
+ FheUint64,
+ FheUint160,
+ TfheCompactPublicKey,
+} from 'node-tfhe';
describe('decrypt', () => {
+ let clientKeySer: Uint8Array;
+ let compactPublicKey: TfheCompactPublicKey;
+
+ beforeAll(async () => {
+ const { clientKey, publicKey } = createTfheKeypair();
+ clientKeySer = clientKey.serialize();
+ compactPublicKey = publicKey;
+ });
+
it('converts a number to bytes', async () => {
const value = BigInt(28482);
const bytes = bigIntToBytes(value);
@@ -24,4 +49,74 @@ describe('decrypt', () => {
const bigint0 = bytesToBigInt(value0);
expect(bigint0.toString()).toBe('0');
});
+
+ it('decryptor bool', async () => {
+ const d = clientKeyDecryptor(clientKeySer);
+ const c = FheBool.encrypt_with_compact_public_key(
+ true,
+ compactPublicKey,
+ ).serialize();
+ const v = await d.decryptBool(toHexString(c));
+ expect(v).toBe(true);
+ });
+
+ it('decryptor 4', async () => {
+ const d = clientKeyDecryptor(clientKeySer);
+ const c = FheUint4.encrypt_with_compact_public_key(
+ 4,
+ compactPublicKey,
+ ).serialize();
+ const v = await d.decrypt4(toHexString(c));
+ expect(v).toBe(4);
+ });
+
+ it('decryptor 8', async () => {
+ const d = clientKeyDecryptor(clientKeySer);
+ const c = FheUint8.encrypt_with_compact_public_key(
+ 67,
+ compactPublicKey,
+ ).serialize();
+ const v = await d.decrypt8(toHexString(c));
+ expect(v).toBe(67);
+ });
+
+ it('decryptor 16', async () => {
+ const d = clientKeyDecryptor(clientKeySer);
+ const c = FheUint16.encrypt_with_compact_public_key(
+ 1700,
+ compactPublicKey,
+ ).serialize();
+ const v = await d.decrypt16(toHexString(c));
+ expect(v).toBe(1700);
+ });
+
+ it('decryptor 32', async () => {
+ const d = clientKeyDecryptor(clientKeySer);
+ const c = FheUint32.encrypt_with_compact_public_key(
+ 77662,
+ compactPublicKey,
+ ).serialize();
+ const v = await d.decrypt32(toHexString(c));
+ expect(v).toBe(77662);
+ });
+
+ it('decryptor 64', async () => {
+ const d = clientKeyDecryptor(clientKeySer);
+ const c = FheUint64.encrypt_with_compact_public_key(
+ BigInt(11200),
+ compactPublicKey,
+ ).serialize();
+ const v = await d.decrypt64(toHexString(c));
+ expect(v).toBe(BigInt(11200));
+ });
+
+ it('decryptor address', async () => {
+ const d = clientKeyDecryptor(clientKeySer);
+ const c = FheUint160.encrypt_with_compact_public_key(
+ BigInt('0x8ba1f109551bd432803012645ac136ddd64dba72'),
+ compactPublicKey,
+ ).serialize();
+ const v = await d.decryptAddress(toHexString(c));
+ expect(v).toBe('0x8ba1f109551bd432803012645ac136ddd64dba72');
+ });
});
diff --git a/src/utils.ts b/src/utils.ts
index a74dc06..face392 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,4 +1,14 @@
import { toBigIntBE, toBufferBE } from 'bigint-buffer';
+import {
+ FheBool,
+ FheUint4,
+ FheUint8,
+ FheUint16,
+ FheUint32,
+ FheUint64,
+ FheUint160,
+ TfheClientKey,
+} from 'node-tfhe';
export const fromHexString = (hexString: string): Uint8Array => {
const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g);
@@ -39,3 +49,30 @@ export const isAddress = function (address: string) {
}
return false;
};
+
+export const clientKeyDecryptor = (clientKeySer: Uint8Array) => {
+ const clientKey = TfheClientKey.deserialize(clientKeySer);
+ return {
+ decryptBool: (ciphertext: string) =>
+ FheBool.deserialize(fromHexString(ciphertext)).decrypt(clientKey),
+ decrypt4: (ciphertext: string) =>
+ FheUint4.deserialize(fromHexString(ciphertext)).decrypt(clientKey),
+ decrypt8: (ciphertext: string) =>
+ FheUint8.deserialize(fromHexString(ciphertext)).decrypt(clientKey),
+ decrypt16: (ciphertext: string) =>
+ FheUint16.deserialize(fromHexString(ciphertext)).decrypt(clientKey),
+ decrypt32: (ciphertext: string) =>
+ FheUint32.deserialize(fromHexString(ciphertext)).decrypt(clientKey),
+ decrypt64: (ciphertext: string) =>
+ FheUint64.deserialize(fromHexString(ciphertext)).decrypt(clientKey),
+ decryptAddress: (ciphertext: string) => {
+ let hex = FheUint160.deserialize(fromHexString(ciphertext))
+ .decrypt(clientKey)
+ .toString(16);
+ while (hex.length < 40) {
+ hex = '0' + hex;
+ }
+ return '0x' + hex;
+ },
+ };
+};