Build FHE Coprocessor on TEE using JavaScript
2024-07-17
Fully Homomorphic Encryption (FHE) boasts a rich history in cryptography, earning its name from its distinctive ability to perform computations directly on encrypted data without exposing the original content. Today, it's leveraged across multiple industries to process sensitive information without compromising user privacy, including fields like medicine, AI, and notably, blockchain. On the blockchain, it offers a unique solution for preserving user privacy on an open ledger, a challenge not adequately addressed by Zero-Knowledge Proofs.
The groundbreaking fully homomorphic scheme, put forth by Craig Gentry in 2009, introduced the quintessential concept of bootstrapping (check his paper for a simple explanation). Major companies such as Google and Microsoft offer tailored FHE solutions, enabling users to run FHE computations on their respective cloud services. Meanwhile, emerging startups like Zama have pioneered FHE solutions using the TFHE schema, which remarkably accomplishes bootstrapping in just a few tens of milliseconds.
Source: Zama blog: Homomorphic Encryption 101
In this article, we will unpack the security risks existing in the current FHE key management scheme. Subsequently, we'll explore how we can integrate Trusted Execution Environments (TEE) into the ecosystem as a two-factor authentication (2FA) solution. We will also shed light on how developers can leverage TEE to build reliable and secure FHE key management utilizing our groundbreaking technology.
MPC is one of the bottleneck of FHE
Before we delve in, it's important to clarify that MPC (more specifically, threshold signature-based MPC) is not inherently a part of the FHE system. However, when projects set out to build a confidential computation platform (such as an fhEVM-based blockchain), they often incorporate an MPC network to manage the global key. This global key is used to encrypt data for all users.
Why is a global key necessary for FHE-based computation?
To comprehend why, let's first understand what FHE computation entails, quoting from Zama’s Homomorphic Encryption 101:
Applied to encryption, this means that operating on plaintexts (i.e., unencrypted data) or on ciphertexts (i.e., encrypted data) will yield an equivalent result — in the clear when operating on plaintexts and under an encrypted form when operating on ciphertexts. For example, given any two ciphertexts c₁ and c₂ respectively encrypting plaintexts x₁ and x₂, there exists a public operation ⊕ such that c₃=c₁ ⊕ c₂ is an encryption of x₃=x₁+x₂.
It’s worth noting that the operation ⊕ can only be applied when c₁ and c₂ are encrypted with the same key. This is true even if the underpinning plaintexts x₁ and x₂ originate from different users.
Consider a real-world use case: we're developing a confidential ERC20 token. Suppose there's a function _mintEncrypted(address to, inEuint128 memory encryptedAmount)
that mints a specified amount
of tokens to a certain address. Within this function, we need to update the totalEncryptedSupply
of this token, as shown below. It would be impossible to perform this operation on totalEncryptedSupply
each time if the encryptedAmount
isn't encrypted with the same key:
function _mintEncrypted(address to, inEuint128 memory encryptedAmount) internal {
euint128 amount = FHE.asEuint128(encryptedAmount);
_encBalances[to] = _encBalances[to] + amount;
totalEncryptedSupply = totalEncryptedSupply + amount;
}
Here's a glance at the architecture overview of Phenix's fheEVM rollups, where the Threshold Network (Service) is recognized as the MPC network in use.
As we mentioned above, due to the practice of writing an FHE application, the key is globally used by all users to encrypt the data they send to the FHE server, which will execute under an encryption state. Thus, the whole security of the system relies on the security of the MPC network, and as we all know the truths of the MPC network are:
- The more nodes you have, the more latency you get
- The fewer nodes you have, the more trust assumptions you need
That said, there have collusion risk inherent in current MPC system that you can not ignore. However, it doesn't mean we can do nothing to mitigate the potential risk. Instead, we can add TEE as 2FA to hedge the risk by moving the key management to TEE (Trusted Execution Environments, a technology to run the program in an isolated zone inside CPU, prove program immutable and limited-accessible).
Why choose TEE?
In the blockchain world, there's a persistent effort to build trust based on cryptographic assurances. In this context, we could generate zero-knowledge proofs (like SNARKs) for every FHE encryption/decryption operation to ensure data are correctly processed. However, FHE introduces substantial overhead compared to plaintext operations, and SNARKs typically are even more computationally expensive than the operation they're set to prove.
This introduces the need for TEE. Compared to the cryptographic approach, the hardware-based solution implemented by TEE doesn’t add extra computation but merely shifts the original computation into an isolated environment within the CPU. By implementing TEE into key generation and data decryption, we avoid significant performance hindrances. This creates a more efficient and secure environment for managing FHE keys.
As illustrated above, MPC nodes of the FHE system are now running inside TEE, instead of producing TEE proof when acting as 2FA for zk-rollups, here TEE is used to protect the key generation progress in the MPC network, and the whole lifecycle of the key is kept inside TEE and never gonna reveal to the outside world, more importantly, the key can not be touched by human even a single piece. TEE itself can guarantee the program it runs is verifiable, it’s impossible for someone can manipulate the state. Also, the data passing between TEE and the client is secured by TLS communication.
With TEE as a 2FA, it can help reduce the risk in an economic way that:
- If TEE is not compromised, there is no chance that collusion can happen;
- If TEE gets compromised, only when collusion happens between nodes that the system is broken.
Before composing this article, we initiated a discussion on the Ethereum Research Forum to expound on our solution. We've incorporated most of the content from that discussion within this article. However, you can gain more insights, such as the advantages and disadvantages of incorporating TEE, by clicking here.
Now, let's shift our focus to how one might execute the FHE key management — in this context, transitioning key generation/sharing and data decryption inside TEE — using JavaScript.
Building FHE Key Management with JavaScript
We have JS Runtime custom-built based on QuickJS. JavaScript code can be deployed to Phala TEE workers. We have engineered a WASM virtual machine based on wasmtime, to facilitate its execution in SGX with the Gramine SDK.
First, you'll need to make some preparations before running the full example, even if you're only developing and don't have an SGX-enabled machine at your disposal. We're assuming that you've already installed the Rust toolchain on your machine. If not, you can refer to this tutorial. To run a JavaScript program example, follow the instructions in this README from the GitHub repository. Once you're familiar with the tutorial, let's explore how to build FHE key management with Zama TFHE wasm binding package.
Currently, we've only attempted key generation with Zama's TFHE library, but theoretically, any other library should operate similarly. Zama has provided WASM binding for their tfhe-rs library, which export client APIs to support key generation, encryption, and decryption. This significantly simplifies the task and requires minimal understanding of the FHE's underlying details. After creating a project with npm
, your first step will be to install the dependencies.
$ npm install node-tfhe
Then, import the necessary modules that you will use later.
import {
CompactFheUint8List,
TfheCompactPublicKey,
TfheConfigBuilder,
TfheClientKey,
ShortintParameters,
ShortintParametersName,
} from "node-tfhe";
Now, let's generate the FHE key using TfheClientKey
. In this function, we will return a clientKey
and a publicKey
. The publicKey
is used to encrypt data sent to the FHE server for computation, and the clientKey
serves as the corresponding private key used to decrypt the returned results from the FHE server.
function createTfheKeypair() {
const block_params = new ShortintParameters(
ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS,
);
const config = TfheConfigBuilder.default()
.use_custom_parameters(block_params)
.build();
const clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
publicKey = TfheCompactPublicKey.deserialize(publicKey.serialize());
return { clientKey, publicKey };
};
Once you've generated the key pair, you can encrypt the data using it. Here, we've provided an example of uint8 encryption and how to decrypt it with the private key.
const value = 100;
const encrypted = encryptUint8(value, publicKey);
console.log(`Encrypted: ${encrypted}`);
// You can sent it to FHE server for computing
// const encrypted = callServer(encrypted);
const compactList = CompactFheUint8List.deserialize(encrypted);
let encryptedList = compactList.expand();
encryptedList.forEach((v) => {
const decrypted = v.decrypt(clientKey);
console.log(`Decrypted: ${decrypted}`);
});
Deploying the code requires some additional configurations. Check the full example at our GitHub repository.
Future work
To develop a fully functional MPC network using JavaScript, there are several important aspects we still need to address. The most critical among these is key sharing among multiple nodes using a secure sharing protocol like Shamir's secret sharing scheme. We're actively exploring solutions and conducting benchmarks in this area. As soon as we make progress, we plan to share our findings with the community. By laying the groundwork now and navigating the challenges, we believe we are paving the way for an advanced FHE key management solution.
Glossary
SGX (Software Guard Extensions)
Intel's Software Guard Extensions (SGX) is a set of security-related instruction codes that implement Trusted Execution Environments (TEEs), allowing parts of a program to be executed in a secure, isolated environment (SGX enclave) and protected from even the host system itself.
Gramine
An open-source library OS (formerly called Graphene-SGX), designed to support running unmodified Linux applications in a secure and efficient environment, primarily relying on SGX for security.
QuickJS
QuickJS is a small and embeddable Javascript engine. It supports the ES2020 specification, including modules, asynchronous generators, and full Annex B support (legacy Web compatibility). It also includes a small binary for basic JavaScript REPL and execution of JavaScript scripts.