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; + }, + }; +};