# SPF Documentation > Complete documentation for Sunscreen SPF (Sunscreen Processing Framework) testnet - Fully Homomorphic Encryption on blockchain This file contains all documentation content in a single document following the llmstxt.org standard. ## Access control Access control ensures only the appropriate parties have access to encrypted data. SPF manages access control during the lifecycle of a ciphertext using an access control list (ACL). The TypeScript client library provides simple functions for managing access permissions. {{#include snippets/init-required.md}} ## Access permission types SPF supports three types of access permissions: - **Run access**: Permission to use the ciphertext as input to a specific FHE program. - **Decrypt access**: Permission to request threshold decryption of the ciphertext. - **Admin access**: Full control over the ciphertext, including the ability to run, decrypt, and grant permissions to other addresses. ## Ciphertext IDs are immutable When access permissions are updated, SPF creates a new ciphertext with the updated ACL and returns a new ciphertext ID. The original ciphertext remains unchanged. This leads to a development flow where ciphertext operations are chained together, with each operation producing a new ciphertext ID that is used in the next operation. Since ciphertexts are immutable, this means that the access cannot be revoked to a ciphertext once it has been granted. Carefully assign your permissions in your applications and provide only the smallest needed permissions to achieve the desired application. ## Granting access permissions The client library provides three convenience functions for common access control operations. Each function grants a single permission and returns a new ciphertext ID. ### Granting run access Grant permission to use the ciphertext as input to a specific FHE program: ```typescript {{#include ../web2-examples/src/acl.ts:simple_allow_run}} ``` **Parameters**: - `signer`: `AnySigner` - Signer for authentication (must have admin access) - `ciphertextId`: `CiphertextId` - The ciphertext identifier - `address`: `Address` - Address to grant run access (use `asAddress()` to convert string to branded type) - `libraryId`: `LibraryId` - The SPF library identifier (program hash) - `programName`: `ProgramName` - The program entry point name - `chainId` (optional): `number` - Chain ID for on-chain (contract) access **Returns**: `Promise` - The new ciphertext identifier with updated ACL This is the most common pattern when users upload encrypted data and need to grant a specific program permission to use it. ### Granting decrypt access Grant permission to request threshold decryption of the ciphertext: ```typescript {{#include ../web2-examples/src/acl.ts:simple_allow_decrypt}} ``` **Parameters**: - `signer`: `AnySigner` - Signer for authentication (must have admin access) - `ciphertextId`: `CiphertextId` - The ciphertext identifier - `address`: `Address` - Address to grant decrypt access (use `asAddress()` to convert string to branded type) - `chainId` (optional): `number` - Chain ID for on-chain (contract) access **Returns**: `Promise` - The new ciphertext identifier with updated ACL Grant this permission to allow another party to view decrypted results without admin privileges. ### Granting admin access Grant full control over the ciphertext, including run, decrypt, and permission management: ```typescript {{#include ../web2-examples/src/acl.ts:simple_allow_admin}} ``` **Parameters**: - `signer`: `AnySigner` - Signer for authentication (must have admin access) - `ciphertextId`: `CiphertextId` - The ciphertext identifier - `address`: `Address` - Address to grant admin access (use `asAddress()` to convert string to branded type) - `chainId` (optional): `number` - Chain ID for on-chain (contract) access **Returns**: `Promise` - The new ciphertext identifier with updated ACL Admin access includes all permissions and the ability to modify the ACL further. ## Common workflow: encrypt, upload, and grant access A typical pattern is for a user to encrypt a value, upload it, and grant run access to a program runner: ```typescript {{#include ../web2-examples/src/acl.ts:grant_run_access}} ``` The user encrypts the value client-side, uploads the ciphertext, and grants run access in a single workflow. The runner can then use the ACL-applied ciphertext ID as input to the program. ## Smart contract access By default, access permissions are granted for addresses. To grant access to smart contracts, specify the `chainId` parameter: ```typescript {{#include ../web2-examples/src/acl.ts:simple_onchain_access}} ``` When `chainId` is provided, the access permission is granted for on-chain use by contracts on that specific blockchain. This enables smart contracts to interact with encrypted data using the SPF network. ## Checking access To verify whether an address has specific access to a ciphertext, use the `checkCiphertextAccess()` function: ```typescript {{#include ../web2-examples/src/acl.ts:check_access}} ``` The function checks all three access types: decrypt, admin, and run. For run access, specify the library hash and entry point for the specific program. **Parameters**: - `ciphertextId`: `CiphertextId` - The ciphertext identifier - `accessType`: `AccessType` - The type of access to check. This is an object that must include: - `type`: The access type (`"admin"`, `"decrypt"`, or `"run"`) - `address`: `Address` - The address to check access for (required) - `chainId` (optional): `number` - Chain ID for checking on-chain (contract) access. Omit for external (wallet) access - For `type: "run"`, also include: - `libraryHash`: `LibraryId` - The SPF library identifier - `entryPoint`: `ProgramName` - The program entry point name **Access type examples**: - `{type: "admin", address: "0x..."}` - Check admin access for an address - `{type: "decrypt", address: "0x...", chainId: 1}` - Check on-chain decrypt access - `{type: "run", address: "0x...", libraryHash: libId, entryPoint: programName}` - Check run access **Returns**: `Promise` - `true` if access is granted, `false` otherwise **Note**: This function does not require authentication. It queries the SPF network to check if a specific address has access to a ciphertext. ### Checking access for other addresses Since the `address` field is required in the access type, you can check access for any address without authentication: ```typescript {{#include ../web2-examples/src/acl.ts:check_access_for_other}} ``` This pattern is useful to verify access permissions for specific addresses before performing operations. No special privileges are required to check access. ## Advanced ACL Operations ### Retrieving access signatures signed by SPF A user can request the SPF create a cryptographic signature proving that an address has specific access to a ciphertext. Smart contracts can then use this signature for on-chain verification that a ciphertext exists with correct permissions and bit size, without querying the SPF network directly. A common use case of this verification is to verify that a ciphertext has the correct bit width and run access to a specific program before requesting a computation. ```typescript {{#include ../web2-examples/src/acl.ts:get_access_signature}} ``` **Parameters**: - `ciphertextId`: `CiphertextId` - The ciphertext identifier - `accessType`: `AccessType` - The type of access to verify. This is an object that must include: - `type`: The access type (`"admin"`, `"decrypt"`, or `"run"`) - `address`: `Address` - The address to verify access for (required) - `chainId` (optional): `number` - Chain ID for checking on-chain access - For `type: "run"`, also include `libraryHash` and `entryPoint` **Returns**: `Promise` with: - `signature`: `Signature` - The cryptographic signature (branded hex string with `0x` prefix) - `message`: `SpfCiphertextAccessConfirmation` - The message that was signed, containing: - `ciphertextId`: The ciphertext identifier - `bitWidth`: The bit width of the ciphertext (useful for on-chain validation) - `access`: The raw access bytes as a hex string - `accessChange`: `AccessChange` - The parsed access change being verified (one of `NewAdmin`, `NewDecrypt`, or `NewRun`) The response includes type guards (`isNewAdmin`, `isNewDecrypt`, `isNewRun`) to safely narrow the `accessChange` type at runtime. These enable TypeScript to provide autocomplete and type safety when inspecting the access change details. **Note**: This function does not require authentication and can be called by anyone to retrieve the access signature for a given address. --- ## Access control(Docs) Access control ensures that only the appropriate parties can decrypt ciphertext data (i.e. can access the underlying plaintext data). Our smart contract library enables developers to request access control changes on ciphertexts stored in SPF. This includes granting run permissions (to allow an entity to run FHE programs using the ciphertext as input) and decryption permissions (to allow an entity to decrypt the ciphertext), as well as other advanced access control changes. ## Preparation of access change items ACL changes happen in two steps. First, you specify the access changes you want to apply to a certain ciphertext. Then, you request the access change to be applied to the ciphertext. Here we cover the first step. ### Creating a run permission The following access change allows an entity to request execution of a certain FHE program using the ciphertext as its input. ```solidity function prepareContractRunAccess(address contractAddress, SpfLibrary lib, SpfProgram prog) returns (SpfAccessChange memory) ``` This is for the case where the entity is a contract address on the current chain. It will require you to provide a "program library / program entry point" combination to limit the run access to a certain FHE program. ### Creating a decrypt permission This access change allows an entity to request decryption of the ciphertext. It comes in two variants: one for allowing a contract to decrypt a value ```solidity function prepareContractDecryptAccess(address contractAddress) returns (SpfAccessChange memory) ``` and one for allowing a end user (wallet) to decrypt a value. ```solidity function prepareSignerDecryptAccess(address signerAddress) returns (SpfAccessChange memory) ``` ## Request an access change After you have prepared all access changes, it's time to request the access change. ```solidity function requestAclAsContract(SpfParameter ciphertext, SpfAccessChange[] memory changes) returns (SpfParameter) ``` This function takes two arguments: - `ciphertext` is the FHE program run parameter created from a single ciphertext that you want to change access on - `changes` is the access changes to be applied to the ciphertext ## Obtain access confirmation Before submitting on-chain transactions, verify access permissions off-chain and obtain a cryptographic signature from SPF using the `spf-client access check` command. **Access types**: `admin`, `decrypt`, `run` **Common parameters**: - `--ciphertext-id`: The ciphertext identifier - `--address`: The address to check access for - `--chain`: Chain identifier (required: `monad`, `sepolia`, or `localhost`) - `--json`: Output full JSON response instead of just signature bytes **Additional parameters for `run` access**: - `--library`: Library identifier (program hash) - `--entry-point`: Program entry point name **Example**: ```sh # Check run access and get signature spf-client access check run \ --ciphertext-id $CIPHERTEXT_ID \ --address $CONTRACT_ADDRESS \ --library $LIBRARY_ID \ --entry-point tally_votes \ --chain monad ``` The signature returned can be used with the verification functions described in the [Verify ciphertext access](#verify-ciphertext-access) section below. ## Verify ciphertext access Some applications may require verifying a ciphertext _could_ be used for running a program (i.e., using the ciphertext won't cause the program to fail because of missing access control conditions or mismatched bit width) or decryption. This can be solved by using the ciphertext access signature. One can request SPF to confirm that a certain ciphertext has a certain access change. If true, SPF will sign some message that encodes this confirmation using the private key. The message format is public and anyone can construct it. The address corresponding to the SPF private key is also public, allowing a contract to use `ecrecover` for verification. The "confirmation" of the ciphertext access is obtained off-chain using the [check access](#obtain-access-confirmation) functionality provided by the client. To verify the signature in a contract, we provided a series of functions for different access items -- all of them will need a data structure of type `SpfParameterSignature` which you can obtain from the signature bytes using this helper function: ```solidity function createSignatureStruct(bytes memory sig) returns (SpfParameterSignature memory) ``` ### Verifying run access To verify that a ciphertext has run permission for a certain FHE program in the current contract, use the following function: ```solidity function verifyCiphertextRunnable( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig, SpfLibrary spfLibrary, SpfProgram spfProgram ) ``` ### Verifying decrypt access To verify that a ciphertext has decrypt permission in the current contract, use the following function: ```solidity function verifyCiphertextDecryptable( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig ) ``` ## Advanced access control changes There are other less common access control changes and verification functions that may be useful in some scenarios. ### Admin permissions This access item adds an entity as the admin (owner) of the ciphertext. Admins have the ability to run programs using the ciphertext as input, decrypt the ciphertext, and manage access control on the ciphertext to other entities. As such, admin access is quite powerful and should be used with care. There are two functions to create this access change: to add a contract as admin ```solidity function prepareContractAdminAccess(address contractAddress) returns (SpfAccessChange memory) ``` and to add a signer as admin. ```solidity function prepareSignerAdminAccess(address signerAddress) returns (SpfAccessChange memory) ``` #### Verifying admin access To verify the admin on a ciphertext, the function to use is ```solidity function verifyCiphertextOwnedBySigner( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig, address signerAddress ) ``` or ```solidity function verifyCiphertextOwnedByContract( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig, address contractAddress ) ``` or ```solidity function verifyCiphertextOwned( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig, ) ``` They look pretty similar; the differences are the first uses signer address, the second uses the provided contract address, while the last uses current contract. ### Less common run permissions The following access item allows an entity to request execution of a certain FHE program using the ciphertext as its input. The function you use is ```solidity function prepareSignerRunAccess(address signerAddress, SpfLibrary lib, SpfProgram prog) returns (SpfAccessChange memory) ``` This is for the case where the entity is a signer address. It will require you to provide a "program library / program entry point" combination to limit the run access to a certain FHE program. ### Less common verifications Some additional verification options (that are less likely to be used) are included below. #### Run access verification We can verify that some contract (potentially not the current contract) has run access on a ciphertext using the following function. ```solidity function verifyCiphertextRunnableByContract( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig, address contractAddress, SpfLibrary spfLibrary, SpfProgram spfProgram ) ``` #### Decrypt access verification The first option uses the signer address while the second option uses the contract address on the current chain ```solidity function verifyCiphertextDecryptableBySigner( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig, address signerAddress ) ``` or ```solidity function verifyCiphertextDecryptableByContract( SpfParameter memory parameter, uint8 bitWidth, SpfParameterSignature memory sig, address contractAddress ) ``` --- ## What is the Sunscreen SPF? Sunscreen's Secure Processing Framework (SPF) is a comprehensive end-to-end platform that enables writing, deploying, and using FHE programs on-chain or off-chain. It consists of 3 major pieces: - **Core stack**: Built on our [Parasol](https://docs.sunscreen.tech/) offering. This includes the compiler, processor, and low-level FHE library which enables you to create your FHE program in C (in the future Rust) and get great performance, without having to be a cryptography expert. - **Control stack**: The control stack helps with all auxiliary functions that are needed to actually use FHE programs in a real world setting. This includes things like data storage (since FHE-encrypted data is too large to store directly on-chain), access control (to ensure only the proper users have access to sensitive data), key management (we set up a threshold committee to respond to decryption requests), and running your program (which we handle on your behalf as a powerful machine is needed to get good performance). The control stack exposes some APIs through HTTP endpoints, usually known as SPF service, for the client to interact with. - **Data bus**: This can be thought of as a [pull oracle](https://docs.pyth.network/price-feeds/pull-updates#pull-oracles) that monitors the blockchain for requests of program run, access change and decryption, forwards these requests to our SPF service for execution, and posts result back on-chain if needed. For web2 use cases, users will directly interact with the SPF service. # Why we took this approach The main challenges we see with FHE today are that: - Creating highly optimized FHE programs is incredibly challenging. You generally need to restructure your program and learn an eDSL to work with the tech. Importantly, there's a very large performance gap between what you can get as an FHE expert vs non-expert, as you must know how to efficiently structure your program as FHE circuits and choose FHE scheme parameters that are secure yet performant. - FHE is in active R&D, with the expectation that new schemes will emerge and specialized hardware may become available to greatly accelerate performance. How can developers set themselves up for success given the shifting grounds? We address these issues by building the SPF, which enables developers to write programs directly in a mainstream programming language and get highly optimized performance automatically. Moreover, our architecture is modular, scaling as more powerful hardware becomes available and allowing us to swap out the underlying FHE scheme. You'll notice that nothing about the FHE programs written by developers using our SPF is scheme dependent. # Value proposition Accordingly, our focus is on improving the developer experience so that more developers can successfully build and deploy FHE apps on any chain, along with improving the performance developers obtain as the hardware evolves. Developer Experience: - **Write in mainstream programming languages.** Using SPF, you can write FHE programs directly in C. The only changes you need to make to your code (with some caveats) are those indicating which functions should be treated as FHE programs and which inputs/outputs should be kept hidden. - **Write once, use anywhere.** Developers need the freedom to deploy their program wherever they see fit, without having to rewrite complex program logic for new ecosystems. Accordingly, the SPF allows developers to create their program once and use it anywhere (on and off-chain). Performance: - **Automatically optimized.** Under the hood of our SPF is our compiler and processor that optimizes program performance on behalf of the developer using a novel homomorphic computing paradigm designed in-house. Learn more about this in our [research paper](https://eprint.iacr.org/2025/1144). - **Built for the hardware endgame.** Our computing approach seeks to maximize throughput and minimize latency by extracting parallelism and reducing the critical path in the circuit. If you'd like to learn more about this, we'd recommend [this blog post](https://blog.sunscreen.tech/a-new-vision-for-tfhe-and-compilers/). # Relationship between Parasol and SPF [Parasol](https://docs.sunscreen.tech/) is our "core stack" allowing developers to create highly optimized FHE programs using our variant of TFHE (Torus FHE) scheme. It can be used on a standalone basis. Unlike SPF, Parasol is _not_ a service, meaning you would need to set up your own machine to run FHE programs. Think of Parasol as a proper subset of the SPF. Parasol powers our SPF but SPF includes many more services beyond what Parasol offers (e.g. delegated computation, data storage, key management). --- ## Benchmarks Please see our dedicated [repo](https://github.com/Sunscreen-tech/spf-benchmark) for benchmarks and how to run them! --- ## Compiling FHE programs Say we had the following FHE program: ```c #include [[clang::fhe_program]] void add([[clang::encrypted]] uint8_t a, [[clang::encrypted]] uint8_t b, [[clang::encrypted]] uint8_t *result) { *result = a + b; } ``` To compile this program, use `clang` from the [Parasol compiler](inst.md#parasol-compiler) as following: ```sh # Compile the program with clang, targeting the parasol CPU with # optimizations enabled (-O2). Specify the input C file and the # output binary path. clang \ -target parasol \ -O2 \ path/to/voting.c \ -o path/to/voting ``` This will produce a small binary file at `path/to/voting`. This file is an [Executable and Linkable Format (ELF)](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) file that contains some metadata about the Parasol programs, the specific FHE programs specified with `[[clang::fhe_program]]`, and the assembly code to run the FHE programs on the Parasol processor. The file generated here can be used with SPF to execute the program, as you probably have seen in the [web2](example2.md) and [web3](example3.md) quick start demos. --- ## Decryption After an FHE program executes on encrypted inputs, the plaintext result can be retrieved through threshold decryption. {{#include snippets/init-required.md}} ## Requesting threshold decryption To request threshold decryption of a ciphertext, use the `requestDecryption()` function: ```typescript {{#include ../web2-examples/src/decrypt.ts:request_decryption}} ``` This submits a decryption request to the SPF threshold committee. The signer must have decrypt access to the ciphertext. **Parameters**: - `signer`: `AnySigner` - Signer for authentication (must have decrypt access) - `ciphertextId`: `CiphertextId` - The ciphertext identifier to decrypt **Returns**: `Promise` - The decryption handle (branded hex string with `0x` prefix) ## Checking decryption status To check the status of a decryption request, use the `checkDecryptionStatus()` function: ```typescript {{#include ../web2-examples/src/decrypt.ts:check_decryption_status}} ``` **Parameters**: - `decryptHandle`: `DecryptHandle` - The decrypt handle from `requestDecryption()` - `bitWidth`: `8 | 16 | 32 | 64` - The bit width of the encrypted value - `signed`: `boolean` - Whether the value is signed (default: false) **Returns**: `Promise` - Discriminated union with `status` field ## Waiting for decryption completion For convenience, use `waitForDecryption()` to automatically poll until decryption completes and parse the result: ```typescript {{#include ../web2-examples/src/decrypt.ts:wait_for_decryption}} ``` This function polls `checkDecryptionStatus()` until decryption completes, then automatically parses the polynomial bytes into the plaintext value. **Parameters**: - `decryptHandle`: `DecryptHandle` - The decrypt handle from `requestDecryption()` - `bitWidth`: `8 | 16 | 32 | 64` - The bit width of the encrypted value - `signed`: `boolean` - Whether the value is signed (default: false) - `signal`: `AbortSignal` - Optional cancellation signal (use `AbortSignal.timeout(ms)` for timeout) **Returns**: `Promise` - The decrypted plaintext value ### Cancellation support The `waitForDecryption()` function supports cancellation via `AbortSignal`: ```typescript {{#include ../web2-examples/src/decrypt.ts:decryption_with_abort}} ``` ### Complete decryption workflow Here is a complete decryption example: ```typescript {{#include ../web2-examples/src/decrypt.ts:complete_decryption}} ``` --- ## Decryption(Docs) After an FHE program executes on encrypted inputs, the plaintext result can be retrieved through threshold decryption. This section covers requesting decryption and receiving results via [callback functions]() in Solidity smart contracts. ## How the callback pattern works When you request decryption, you specify which function in your contract should receive the result. SPF processes the decryption through the threshold decryption committee, then calls your specified function with the plaintext value. This allows your contract to store the decrypted result or trigger further logic based on the value. ```mermaid sequenceDiagram participant Contract as Your Contract participant SPF as SPF Service participant Committee as Threshold Decryption Committee Contract->>SPF: requestDecryptionAsContract( callback.selector, ciphertextId) Note over Contract,SPF: Contract specifies which functionshould receive the result SPF->>Committee: Request threshold decryption Committee->>Committee: Decrypt ciphertext Committee->>SPF: Return plaintext result SPF->>Contract: callback(ciphertextId, plaintext) Note over Contract: Callback function processesthe decrypted value ``` ## Requesting threshold decryption To request threshold decryption of a ciphertext, use the `requestDecryptionAsContract()` function: ```solidity function requestDecryptionAsContract(bytes4 functionSelector, bytes32 ciphertextId) ``` This submits a decryption request to the SPF threshold committee. When decryption completes, SPF calls the specified function in your contract with the plaintext result. This enables seamless integration of FHE decryption into your smart contract logic. **Parameters**: - `functionSelector`: `bytes4` - The function the SPF calls with the decrypted result. Use `functionName.selector` to get the bytes4 [selector](https://rareskills.io/post/function-selector). - `ciphertextId`: `bytes32` - The ciphertext identifier to decrypt (obtained from `getOutputHandle()` and converted with `passToDecryption()`). ## Implementing the callback function The callback function receives the decrypted plaintext value from SPF. The function signature must match `(bytes32, uint256)`: ```solidity function decryptionCallback(bytes32 identifier, uint256 result) onlyThresholdDecryption { // Process the decrypted result plaintext_field_in_contract = result; } ``` **Important**: Use the `onlyThresholdDecryption` modifier to ensure only SPF can call this function. To pass this example function to `requestDecryptionAsContract()`, use `decryptionCallback.selector`. ## Converting output parameters Program outputs from `getOutputHandle()` return `SpfParameter` type. To convert to the `bytes32` ciphertext identifier required by `requestDecryptionAsContract()`, use the `passToDecryption()` helper: ```solidity function passToDecryption(SpfParameter memory param) returns (bytes32) ``` This converts the parameter format to the ciphertext identifier format expected by the decryption function. --- ## Downloading programs and ciphertexts To maintain local copies of FHE programs and encrypted inputs, both programs and ciphertexts can be downloaded using the TypeScript client library. {{#include snippets/init-required.md}} ## Program download To download a previously uploaded FHE program from SPF, use the `downloadProgram()` function: ```typescript {{#include ../web2-examples/src/download.ts:download_program}} ``` Program downloads do not require authentication. Anyone can download a program library if they know its library ID. **Parameters**: - `libraryId`: `LibraryId` - The library identifier (branded hex string with `0x` prefix) **Returns**: `Promise` - The program binary data ## Ciphertext download To download a ciphertext from SPF, use the `downloadCiphertext()` function: ```typescript {{#include ../web2-examples/src/download.ts:download_ciphertext}} ``` **Important**: Downloading ciphertexts requires the signer to have access to the ciphertext but there is no dedicated download permission - it uses decryption permission. See the [Access Control](acl.md) page for details. **Parameters**: - `signer`: `AnySigner` - Signer for authentication (must have decrypt access) - `ciphertextId`: `CiphertextId` - The ciphertext identifier (branded hex string with `0x` prefix) **Returns**: `Promise` - The ciphertext binary data The function throws an error if the signer lacks decrypt access to the specified ciphertext. --- ## Compile the voting program with clang, targeting the parasol CPU ## Private Voting Made Easy with Parasol We want to run a voting system for an issue near and dear to our hearts: whether to adopt a dog as our office pet. We could poll everyone in the office to see if they approve of the idea, but that would reveal how each person voted; no one wants to be known as the person who looked Fido deep into their big, brown eyes and said you can't be here. Instead, we can use Fully Homomorphic Encryption (FHE) to tally the votes without revealing who would prefer Fido to stay at home. To implement this secure voting system to keep Fido, we'll use the Parasol framework to compile and run a simple voting program. This program will accept encrypted votes either that either vote in favor or against Fido, tally them, and return the Fido's fate without ever revealing individual votes. ```c {{#include ../../fhe-programs/src/voting.c}} ``` The program is standard C code with two FHE-specific annotations: - `[[clang::fhe_program]]` marks the function as an FHE program, and - `[[clang::encrypted]]` marks parameters that will be passed in as ciphertexts. To compile the program, save the code to a file (e.g., `voting.c`) and use the Parasol compiler to compile the result into an FHE program. ```sh # Compile the voting program with clang, targeting the parasol CPU # and enabling optimizations. clang \ -target parasol \ -O2 \ voting.c \ -o voting ``` Congratulations! You've just compiled your first FHE program 🎉. But how do we use the program to run the auction in practice? Luckily we provide the Sunscreen Secure Processing Framework (SPF) service, which allows developers to upload, run, and decrypt the results of FHE programs easily. This enables developers to focus on building their applications without worrying about the complexities of FHE or managing the underlying compute infrastructure. --- ## Setup Clang For the FHE programs, we will need the version of `clang` that supports the Parasol processor. Information can be found for [installing clang](inst.md#parasol-compiler). For this example we will assume you have the sunscreen version of `clang` as the first `clang` in your path. If you are unsure, you can run `clang --version` to check if it mentions the Sunscreen LLVM repository. --- ## Upload the compiled voting program to SPF. The command outputs ## Uploading the voting program To use the compiled program on-chain, the binary must be stored by the SPF service. The SPF client provides an upload utility that stores the program and returns a unique identifier for referencing the program in subsequent operations. ```sh # Upload the compiled voting program to SPF. The command outputs # the library identifier, which is stored in LIBRARY_ID. export LIBRARY_ID=$(spf-client upload-program --file voting) ``` The `upload-program` command outputs the library's identifier. For this example, the library identifier is `0x7f7e6993da0d371c4f7f4573df315fe3638d253b74c3f98e946f9b9737b5b51b`. This identifier is referenced later when configuring the smart contract and requesting program runs. --- ## Web2 Multi Wallet --- ## End-to-end private voting on the web This section walks through building and deploying an encrypted voting application using SPF. This example demonstrates the web2 approach, where applications interact directly with the SPF REST API using the TypeScript client library. Our demo is available [here](https://voting-demo.sunscreen.tech/). ## Prerequisites: setting up the development environment This example requires two development environments: the Parasol compiler for FHE programs (written in C) and the TypeScript client for interacting with SPF. ### Installing the SPF client The `spf-client` package is available on npm. Create a new project directory and install dependencies: ```sh mkdir voting-example cd voting-example # Initialize a new node project npm init -y # Install spf-client from npm npm install @sunscreen/spf-client # Install tsx for running TypeScript examples npm install --save-dev tsx # Configure package.json to use ES modules npm pkg set type=module ``` {{#include ./example/compile-program.md}} ## Creating a web app for private voting While we could run this example locally [using Rust](https://www.github.com/sunscreen-tech/spf), it would be nice to have a simple web application where multiple voters can submit their encrypted votes, and Sunscreen's Secure Processing Framework (SPF) can tally the results. Here we will demonstrate using the TypeScript client to interact with SPF to build a deployed voting application using the compiled program and the SPF service. ### Scenario In this scenario, we will have the following parties involved: **Voters**: Each voter independently encrypts their vote (1 for yes, -1 for no). Voters will upload their encrypted votes to SPF for tallying. Individual votes remain hidden for the entire voting lifecycle. **Runner**: The runner coordinates the vote tallying (only learning the outcome, not any individual votes). The runner uploads the voting program to SPF, collects encrypted votes from voters, submits the computation request to SPF, and finally decrypts the result. ### Initializing the SPF client Before interacting with SPF, you'll need to import the `spf-client` package. We use a namespace import to keep the code clean and make it clear where functions come from: ```typescript {{#include ../examples/voting-web2/voting.ts:imports}} ``` With this namespace import, all SPF functions are accessed via the `spf.` prefix (e.g., `spf.initialize()`, `spf.uploadProgram()`). Initialize the client. By default, it connects to the production SPF service at `spf.sunscreen.tech`: ```typescript // Initialize the client (connects to spf.sunscreen.tech by default) await spf.initialize(); ``` ### Step 1: Uploading the voting program First we have to upload the compiled program to SPF. This step only needs to be done once, and the resulting library ID can be reused for all future runs of this program. ```typescript // Read the compiled program file const programPath = "voting"; // Compiled binary from previous step const programBytes = new Uint8Array(readFileSync(programPath)); // Upload to SPF const libraryId: string = await spf.uploadProgram(programBytes); console.log("Library ID:", libraryId); // Example output: 0x7f7e6993da0d371c4f7f4573df315fe3638d253b74c3f98e946f9b9737b5b51b ``` The `spf.uploadProgram()` function sends the `voting` binary to SPF and returns a unique library ID that identifies this program. We will need this ID later when submitting program runs, so be sure to save it. ### Step 2: Encrypt and upload votes Each voter independently encrypts their vote value, uploads the ciphertext to SPF, and grants run access to the runner. The `encryptUploadAndGrantAccess` function below encapsulates this workflow: ```typescript {{#include ../examples/voting-web2/voting.ts:helper}} ``` In this function, the voter performs three main actions: 1. **`spf.encryptValue(approveAsInt, 8)`**: Encrypts the vote as an 8-bit ciphertext using the client library. The value is automatically treated as signed when negative. This happens entirely client-side; the plaintext vote is never sent to SPF. 2. **`spf.uploadCiphertext(signer, ciphertext)`**: Uploads to the vote to SPF, returning a unique ciphertext ID owned by the voter. A vote signer can be created using `spf.PrivateKeySigner.random()`; it is up to the application developer to determine the most secure way to manage user credentials (also known as keys). 3. **`spf.allowRun(...)`**: Grants the runner permission to use this ciphertext as input to the `tally_votes` program. This returns a new ciphertext ID with the access control list (ACL) applied. Every ciphertext is _immutable_; applying access control creates a new version of the ciphertext with the updated permissions, while the original remains unchanged. Each voter follows this pattern independently. The runner collects the ACL-applied ciphertext IDs for the next step. ### Step 3: Tally the votes privately The runner collects all votes and submits the computation request to SPF. ```typescript {{#include ../examples/voting-web2/voting.ts:submit_run}} ``` The `parameters` that are submitted for a run must match those in the signature of the `tally_votes` program. ```c,ignore void tally_votes(int8_t* votes, uint16_t num_votes, bool* didTheIssuePass) ``` The `spf.submitRun()` function returns a run handle—a unique identifier for this computation. The `spf.waitForRun()` function polls until the SPF service completes the encrypted computation. ### Step 4: Decrypt the result After computation completes, the runner decrypts the encrypted result: ```typescript {{#include ../examples/voting-web2/voting.ts:decrypt}} ``` **Decryption workflow**: 1. **Derive result ciphertext ID**: Use `spf.deriveResultCiphertextId(runHandle, 0)` to get the ID of the first output ciphertext 2. **Request decryption**: Sends the ciphertext to SPF's threshold decryption committee 3. **Wait for result**: The committee decrypts collaboratively and returns the plaintext value The result `plaintext` contains an 8-bit integer: - `1` if the vote passed (sum of votes > 0) - `0` if the vote failed (sum ≤ 0) ## Live demo A live demo of the voting application is available [for you to explore](https://voting-demo.sunscreen.tech), which demonstrates how one might build a simple web interface around the above code. You can see the source code for the web app [here](https://github.com/Sunscreen-tech/spf-client/tree/main/browser-demo). ## SPF makes it easy to build privacy-preserving applications As this demo shows, SPF makes it easy to build privacy-preserving applications using FHE. The TypeScript client library provides a straightforward interface for uploading programs, encrypting data, managing access control, and running computations. The developer does not need to worry about the complexities of FHE or managing the underlying compute infrastructure. Users can be assured that their data remains private throughout the entire process. What do you think Fido's fate should be? Let us know on [Discord](https://discord.gg/sunscreen) or [Twitter / X](https://x.com/sunscreentech). ## Appendix: Complete example code The full code for this example is shown below. To run it, copy the code into a file named `voting.ts`, then set up and run the project: ```sh # Create a new project directory mkdir voting-example cd voting-example # Copy the code below into a file named voting.ts # Initialize the project with dependencies npm init -y npm pkg set type=module npm install @sunscreen/spf-client npm install --save-dev tsx # Run the example npx tsx voting.ts ```
Click to view the complete example code ```typescript {{#include ../examples/voting-web2/voting.ts}} ```
--- ## End-to-end private voting on the blockchain This section walks through building and deploying an encrypted voting application using SPF. This example demonstrates the web3 approach, where smart contracts coordinate FHE computations on the blockchain. Contracts submit computation requests to SPF, and results are posted back on-chain via threshold decryption callbacks. Our demo is available [here](https://voting-demo.sunscreen.tech/). ## Prerequisites: setting up the development environment This example requires two development environments: the Parasol compiler for FHE programs (written in C) and Foundry for smart contract development and deployment. This example uses [Foundry](https://getfoundry.sh/) for contract development and deployment, but any Ethereum development framework (Hardhat, etc.) can be used. The key requirement is access to the `sunscreen-contracts` library, which provides the SPF integration components. We will be using the [Monad testnet](https://testnet.monad.xyz/), but this example works equally well today on the Ethereum Sepolia testnet. The only changes are the RPC URL (a free one is `https://ethereum-sepolia-rpc.publicnode.com`) and any command referencing `--chain monad` as an input should use `--chain sepolia` instead. ### Setting up Foundry Create a new Foundry project for the smart contract: ```sh # Create a new forge environment in the folder `voting` forge init voting-example cd voting-example # Install SPF contracts (using HTTPS URL) forge install https://github.com/Sunscreen-tech/sunscreen-contracts.git ``` After installing the contracts, configure the import remappings in `foundry.toml`: ```toml [profile.default] src = "src" out = "out" libs = ["lib"] remappings = [ "@sunscreen/=lib/sunscreen-contracts/" ] ``` This remapping allows you to import the SPF contracts in your Solidity code with `import {Spf} from "@sunscreen/contracts/Spf.sol";`. {{#include ./example/compile-program.md}} For web3 applications, you typically upload the program to SPF before you build your application as the output of the upload, known as the library identifier, will need to be referenced by the contract. To do so, use the [`spf-client`](https://github.com/Sunscreen-tech/spf-client) CLI. The `upload-program` command stores the program and returns a unique library identifier for referencing the program in subsequent operations. ```sh spf-client upload-program --file voting ``` ## Building the voting application While the web2 approach uses direct API calls, blockchain applications coordinate FHE computations through smart contracts. This example demonstrates using a Solidity contract to manage encrypted voting on the Monad testnet. ### Scenario In this scenario, the following parties are involved: **Contract deployer**: Uploads the voting program to SPF, deploys the smart contract to the blockchain, and configures the contract with the program library ID. **Voters**: Each voter independently encrypts their vote (1 for yes, -1 for no), uploads the encrypted vote to SPF, grants the smart contract permission to use their ciphertext, and submits the ciphertext ID to the contract. Individual votes remain hidden throughout the entire voting lifecycle. **Smart contract**: The contract collects encrypted votes, triggers the FHE computation on SPF, requests threshold decryption of the result, and receives the decrypted outcome via callback. The contract learns only the final tally, never individual votes. ### Step 1: Write and deploy the smart contract The voting contract integrates with SPF to coordinate encrypted computation. The contract calls the `tally_votes` function defined in the C program: ```solidity {{#include ../contracts/Voting.sol}} ``` The contract implements four key components: 1. **Inheritance**: The contract inherits from `TfheThresholdDecryption` (enabling threshold decryption callbacks) and `ISpfSingleProgram` (allowing `spf-client` to automatically populate program information). 2. **Vote submission**: The `submitVote` function accepts encrypted vote ciphertext IDs, allowing the contract to collect votes before tallying. 3. **Tally execution**: The `tallyVotes` function packages votes as parameters, calls `requestRunAsContract` to trigger FHE computation on SPF, and calls `requestDecryptionAsContract` to decrypt the result. The computation returns a run handle, and output ciphertext IDs are derived using `getOutputHandle`. The threshold decryption request specifies `postIssuePassedCallback` as the callback function for receiving the decrypted result. 4. **Result callback**: The `postIssuePassedCallback` function receives the decrypted tally from the SPF threshold decryption service. The `onlyThresholdDecryption` modifier ensures only SPF can call this function, preventing unauthorized result posting. #### Deploying the contract To deploy the contract, set environment variables for the RPC endpoint URL (`$RPC_URL`) and wallet private key (`$PRIVATE_KEY`). For Monad testnet, a free RPC endpoint is available at `https://testnet-rpc.monad.xyz`. ```sh # Deploy the Voting contract using Foundry. This broadcasts the # transaction and outputs JSON with the deployed contract address. forge create \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ Voting.sol:Voting \ --broadcast \ --json ``` The command returns JSON with the deployed contract address in the `deployedTo` field. Store this address in `$CONTRACT_ADDRESS` for subsequent commands. Optionally, use [`jq`](https://jqlang.org/) to extract and export the contract address in a single command: ```sh export CONTRACT_ADDRESS=$(forge create --rpc-url $RPC_URL --private-key $PRIVATE_KEY Voting.sol:Voting --broadcast --json | jq -r '.deployedTo') ``` ### Step 2: Encrypt and upload votes The SPF client can generate and upload ciphertexts that encrypt input values for FHE programs. ```sh # Generate an encrypted ciphertext for an 8-bit value. # For negative values, use --value=-1 syntax (with equals sign). export CIPHERTEXT_ID=$(spf-client generate-ciphertext \ --value=-1 \ --bits 8 \ --upload \ --private-key $PRIVATE_KEY) ``` You will need to do this once for each vote. In practice, each voter would run this command independently to encrypt and upload their vote, each with their own private key. ### Step 3: Grant access to the contract Ciphertexts are private by default. To allow the smart contract to use the ciphertext as input to the `tally_votes` program, the voter must grant the contract permission. This is done using the `access grant run` command, specifying the contract address as the executor. ```sh # Grant run access to the contract. The library and entry point are # automatically queried from the contract (which implements # ISpfSingleProgram). This returns a new ciphertext ID with updated ACL. export RUNNABLE_CIPHERTEXT_ID=$(spf-client access grant run \ --ciphertext-id $CIPHERTEXT_ID \ --executor $CONTRACT_ADDRESS \ --chain monad \ --private-key $PRIVATE_KEY) ``` As each ciphertext is immutable, the command outputs a new _ciphertext ID_ with the access control applied. This new ID can then be submitted to the contract in the next step. ### Step 4: Run the vote tally With encrypted inputs prepared and access control configured, votes can be submitted to the smart contract. The contract workflow consists of three steps: submitting individual votes, triggering the encrypted tally, and retrieving the result. #### Submitting votes The contract's `submitVote` function accepts individual ciphertext identifiers. Submit each vote separately using the access-controlled ciphertext identifiers from the previous step, for example: ```sh cast send --rpc-url $RPC_URL $CONTRACT_ADDRESS --private-key $PRIVATE_KEY "submitVote(bytes32)" $RUNNABLE_CIPHERTEXT_ID ``` Repeat this command for each vote, replacing the ciphertext identifier each time. Here we can see the privacy benefit of FHE: the contract collects votes without ever seeing the plaintext values, only the encrypted ciphertext IDs. #### Tallying votes and requesting decryption After all votes are submitted, call `tallyVotes()` to initiate the FHE computation. This function automatically triggers both the encrypted vote tally and threshold decryption: ```sh cast send --rpc-url $RPC_URL $CONTRACT_ADDRESS --private-key $PRIVATE_KEY "tallyVotes()" ``` The function performs the following steps automatically: 1. Packages the votes and parameters for the FHE program. 2. Requests SPF to execute the `tally_votes` program off-chain. 3. Derives the output ciphertext ID from the computation result. 4. Requests threshold decryption of the encrypted result. #### Retrieving results After the decryption completes, the SPF service calls the contract's `postIssuePassedCallback` function with the decrypted result. The contract stores the result in the `didTheIssuePass` state variable. To query the contract for the voting result, call the `getDidTheIssuePass()` function. ```sh cast call --rpc-url $RPC_URL $CONTRACT_ADDRESS "getDidTheIssuePass()" ``` This returns `true` if the issue passed (sum of votes greater than 0), or `false` otherwise. ## Live demo A live demo of the voting application is available [for you to explore](https://voting-demo.sunscreen.tech), which demonstrates how one might build a simple web interface around the above code. You can see the source code for the web app [here](https://github.com/Sunscreen-tech/spf-client/tree/main/browser-demo). ## SPF enables privacy-preserving blockchain applications This example demonstrates how SPF integrates with blockchain applications to enable private computation on encrypted data. Smart contracts coordinate FHE computations through SPF, while the Solidity contract library handles the complexity of program execution and threshold decryption. Access control ensures only authorized contracts can use encrypted data, and threshold decryption brings results on-chain through secure callbacks. Throughout the entire process, individual votes remain encrypted and private. What do you think Fido's fate should be? Let us know on [Discord](https://discord.gg/sunscreen) or [Twitter / X](https://x.com/sunscreentech). --- ## FAQ ## How quickly can I get back the decrypted value of an FHE program? This depends on three things -- how long it takes the FHE program to be run, when a decryption is requested, and the block time of the host chain (for web3 use cases). To minimize latency, request the program run and decryption together. Our ciphertext waiting feature allows this. In web3, the quickest the decrypted value can be returned is the subsequent block (meaning the bottleneck may be the chain's block time and nothing FHE related, this is particularly true on chains with longer block times such as 12 seconds for Ethereum). ## Can the threshold committee members holding the secret key shares see my data? No they cannot, assuming there is an honest majority in the threshold committee. This is similar to the assumption of an honest majority in blockchain consensus. Threshold committee members are chosen based on reputation and we will increase the number of members in the threshold committee over time. ## How do I know the program will be run correctly? Today, you can repeat the computation yourself locally (i.e. re-run the FHE programs with the input ciphertexts to check it returns the claimed output ciphertext) to verify that the program was run correctly. --- ## What features does SPF offer? This section covers FHE programs supported today and SPF's current features. Additional features are in active development (see [Upcoming features](#upcoming-features)). ### General features - Deploy privacy-preserving programs directly on Ethereum Sepolia and Monad (testnet). **Note: If there's a new chain you really want, [drop us a message](mailto:ravital@sunscreen.tech).** - Delegated computation, meaning we run the FHE program off-chain and post the results back to the chain via a pull-style oracle. - Storage management. We store FHE-related data on your behalf so you don't have to worry about this. - Key management. We've set up a threshold committee in which different members hold a share of the private key and the decryption will require at least a certain number of members to participate to avoid collusion. - Access control. We have built an access control mechanism to allow users to specify who can run programs on and decrypt their ciphertexts. SPF keeps track of who should have access to what data and prevents any unauthorized access. - Re-encryption support. Our current re-encryption scheme uses one time pads (OTP). Future versions will provide additional re-encryption schemes. ### What kinds of FHE programs can you deploy today? When it comes to understanding what _sort_ of programs you can create, we currently support the following: - Unsigned and signed integers up to 32 bits. We provide limited support for 64 bits. - Arithmetic, logical, and comparison operations. - Arrays. - Import statements. - Function inlining. - Programs using a mix of plaintext and ciphertext data. - Branching over plaintext data. - Branching over encrypted data via select. ## Upcoming features We're excited to roll out more features in future releases. ### General features - Integration with more chains. - Improved developer experience and reliability. - Updated threshold implementation for better security guarantees. - Enhanced access control (including proof of plaintext knowledge, proof that ciphertext is well-formed, etc.). ### What kinds of FHE programs will we support in the future? - Higher precision support. - Division. --- ## What is FHE? Fully Homomorpic Encryption is a technology that enables running arbitrary computer programs directly over encrypted data. What's magical about it is that _the data is never decrypted_ during computation -- meaning users don't need to trust the computing party with their data. In particular, FHE schemes support adding and multiplying ciphertexts under the same key. Specifically, for a public key `pk` and encryptions of messages `m_a` and `m_b`, we can produce an encryption of the sum or product of `m_a` and `m_b`: $$ \mathrm{Enc_{pk}}(m_a + m_b) = \mathrm{Enc_{pk}}(m_a) + \mathrm{Enc_{pk}}(m_b) $$ $$ \mathrm{Enc_{pk}}(m_a * m_b) = \mathrm{Enc_{pk}}(m_a) * \mathrm{Enc_{pk}}(m_b) $$ In most FHE schemes, addition is straightforward to implement whereas multiplication is more challenging. Nonetheless, any scheme that supports an unlimited number of both operations can compute anything. There are a number of ways to combine these operations to achieve this goal, but Sunscreen leverages multiplexers to accomplish this. The next few sections describe how Sunscreen internally performs computations. ## Multiplexer gates (aka muxs) Multiplexers are a universal (i.e. they can compute any function) logic element that takes 3 binary inputs: `a`, `b`, `sel`. When `sel = 0`, the multiplexer outputs `a` and when `sel = 1`, it outputs `b`. If you can add and multiply binary values, you can implement a multiplexer: $$ c=(b - a) * sel + a $$ Homomorphic multiplexers (also called CMux) serve as the central element to perform computation in our scheme. They are computationally efficient, simple to compose into functions, and feature good _noise_ characteristics. ## Noise All of today's practical FHE constructions are based on some variant of the Learning With Errors (LWE) problem. These cryptosystems' security requires adding just the _right_ amount of noise during encryption: adding too much noise leads to a garbage value after decryption but adding too little compromises security. Noise is a central issue in FHE because the result of a homomorphic operation contains more noise than its inputs. Noise cascades as one performs computation, with a possibility to eventually accumulate so much noise that the ciphertext becomes garbage (e.g. cannot be decrypted to the correct value). Thankfully, a technique called bootstrapping allows us to reduce the amount of noise in a ciphertext so that we can continue performing computations. ## Circuit bootstrapping A central component of our TFHE scheme variant is a technique known as _circuit bootstrapping_ (CBS). CBS takes a high noise ciphertext of one type (LWE) encrypting a binary message and produces a lower noise ciphertext of another type (GGSW) encrypting the exact same message. ## Ciphertext types There are 3 different types of ciphertexts involved in computation: - **LWE**: a compact ciphertext meant for homomorphic addition only. - **GLWE**: a less compact ciphertext. Supports homomorphic addition just fine. While multiplication works, it incurs tons of noise. - **GGSW**: the largest ciphertext. Supports the required homomorphisms with favorable noise growth. Interestingly, one can also compute a homomorphic multiplication between a GLWE and GGSW ciphertext, producing a GGSW. This is faster than with two GGSWs and still features favorable noise characteristics, especially when the GGSW has low noise (such as right after a circuit bootstrap operation). Using this trick, we can build an efficient multiplexer where $a$, $b$ and the result are GLWE ciphertexts and $sel$ is a GGSW. However, the type mismatch requires careful composition as we will see below. ## Putting it all together The following diagram shows the FHE computation loop: ![Ciphertext conversion](figures/CiphertextConversion.png) The high level goal is to perform general computation using the multiplexer tree (aka the cmux) before going on to reduce the noise (via circuit bootstrapping) so that we can continue performing more computations. The type mismatch between the cmux and circuit bootstrap requires us to perform some additional operations to get the output of the cmux into the "right" form to feed back into the circuit bootstrap. We start by circuit bootstrapping LWE encryptions of bits (e.g. a 32-bit integer) to produce GGSW encryptions of the same bits. These bits are fed to the select lines in a mux tree transcribed from a lookup-table. Running the mux tree results in GLWE outputs of a function, which must be converted before sending them back to the user for decryption or to be used in further computation. Sample extraction and key switching are techniques in FHE that facilitate this conversion process, but we won't bore you with their details. # Other interesting properties FHE features a number of other interesting properties that are useful in building applications for Web3. ## Public key FHE schemes allow for encrypting messages under either a secret (i.e. symmetric) key or a public (i.e. asymmetric) key. This is crucial in web3 as it allows you to encrypt messages under a key you don't know. Sunscreen supports and simplifies some details of efficiently encrypting messages under a public key. ## Threshold FHE With a few modifications, one can use FHE to enable computing over multiple parties' data. We do this by using threshold FHE. Multiple parties each hold a shard of the secret key such that `t` of `n` parties must come together to decrypt a message. Introducing a threshold committee simplifies building multi-user applications as all data can be encrypted with respect to this threshold public key. As such, Sunscreen features a threshold committee to support threshold FHE. ## Post-quantum FHE is post-quantum, meaning there are currently no known "practical" attacks against these schemes using a quantum computer. This is with the obvious caveats that the scheme is set up correctly, has appropriate security parameters, etc. --- ## Installation and initialization Before using any SPF functions, installation and initialization are required. This page covers installing the TypeScript client library, initialization, and creating signers for authentication. ## Installation The `@sunscreen/spf-client` package is available on npm. Install it in your project: ```sh npm install @sunscreen/spf-client ``` The package includes TypeScript definitions and works in both Node.js and browser environments. ## Initialization Before calling any SPF functions, the client must be initialized. This loads the required modules and fetches the SPF public key. All encryption, upload, run, and decryption operations require initialization. ```typescript {{#include ../web2-examples/src/init.ts:basic_init}} ``` ## Creating signers Many SPF operations require authentication via a signer. The client library provides a lightweight `PrivateKeySigner` class, and also supports ethers.js signers (MetaMask, etc.) for compatibility with existing applications should you need it. ```typescript {{#include ../web2-examples/src/init.ts:private_key_signer}} ``` Note that the application developer determines how to securely manage private keys. The `PrivateKeySigner` class does not provide key storage or management features. ## Complete initialization example Here is a complete example showing initialization and signer creation: ```typescript {{#include ../web2-examples/src/init.ts:complete_example}} ``` You can now move on to using other SPF features such as upload ciphertexts, running computations, and decrypting results. --- ## Installation Building, deploying, and running applications on SPF requires several tools: the Parasol compiler for FHE programs, the testnet client for interacting with the network, and optionally Foundry and Rust for smart contract development and local testing. ## Prerequisites - **Operating System**: Linux or macOS (Windows support via [WSL](https://learn.microsoft.com/en-us/windows/wsl/)) - **Command-line proficiency**: Basic familiarity with terminal commands ## Parasol Compiler The Parasol compiler compiles C programs with FHE annotations into bytecode that runs on the Sunscreen Processing Framework (SPF). Compiler binaries are available on the [GitHub releases](https://github.com/Sunscreen-tech/sunscreen-llvm/releases) for the sunscreen-llvm project. **Installation**: 1. Download the tarball for your platform from the latest release 2. Extract the archive to a directory of your choice, e.g., `~/sunscreen-llvm/` 3. Add the `bin/` directory to your PATH Verify the installation: ```sh clang --version ``` Output should resemble the following: ``` clang version 18.1.6 (git@github.com:Sunscreen-tech/sunscreen-llvm.git 9ead3b9fc2d01f1bc62e120a0a3944279c3a4039) Target: unknown Thread model: posix ``` ## Testnet Client The testnet client provides a command-line interface for interacting with the Sunscreen testnet, including deploying FHE programs and submitting computation requests. You can either download the the standalone SPF client CLI from our [GitHub release page](https://github.com/Sunscreen-tech/spf-client/releases) or by building from source from that repository. ## Foundry (Optional - Smart Contract Development) Foundry is a development toolkit for Ethereum-compatible chains, providing tools to build, deploy, and interact with smart contracts. The examples in this documentation use Foundry for smart contract integration with SPF. **Installation**: ```sh curl -L https://foundry.paradigm.xyz | sh foundryup ``` ### Installing Node.js (Optional - Web2 Development) The web2 approach uses the TypeScript spf-client library to interact directly with the SPF REST API. If Node.js is not already installed, download it from [nodejs.org](https://nodejs.org/) or install via a package manager ```sh # macOS (via Homebrew) brew install node # Ubuntu/Debian sudo apt install nodejs npm ``` ## Next Steps With the required tools installed, proceed to the [web2 example](example2.md) or [web3 example](example3.md) to write, deploy and use your first FHE program with SPF. --- ## Introduction [Fully homomorphic encryption](https://blog.sunscreen.tech/an-intro-to-fully-homomorphic-encryption-for-engineers/) (aka FHE) enables running arbitrary computer programs directly over encrypted data without ever decrypting that data. This allows us to improve existing applications across finance, machine learning and much more. Imagine being able to trade on-chain while protecting your alpha or create AI agents trained on any data, no matter how sensitive. FHE provides users strong guarantees that their data is never revealed to anyone, not even the application developer or the cloud provider running the computation. Data breaches become a thing of the past, and users can safely use applications without worrying about their data being leaked or misused. Unfortunately, integrating FHE technology into an application is incredibly challenging. To get good performance with FHE, the application developer needs to understand quite a bit about the underlying math while still ensuring the parameters are secure. Furthermore, additional infrastructure needs to be set up (from storage to key management and access control). These challenges serve as a significant barrier to entry, and has limited the adoption of FHE in real-world applications thus far. That's why we have built the Secure Processing Framework (SPF), a comprehensive solution that allows developers to easily create, deploy, and use FHE programs in web2 and web3. We handle all the hard tasks on your behalf -- you write normal apps as usual while we optimize and provide the infrastructure to run your programs. With SPF, web3 users benefit from blockchain's decentralization, transparency and verifiability, without having to make their data public on a public blockchain. They can bid in auctions, play poker, vote, and trade, with the actual contents (bids, hands of poker, votes, orders) encrypted and known only to themselves, while the state transition is verifiable in accordance with web3's paradigm -- the best of both worlds! Currently, you can use SPF on Ethereum's testnet (Sepolia) and Monad (testnet). If you'd like another chain supported, [please drop us a message](mailto:ravital@sunscreen.tech). --- ## Current limitations There are a few limitations on what you can do with FHE programs today. ## Integer operations We currently support integers up to 64 bits. We will add support for higher precision integers in later releases. 64 bit support does have a caveat; constants are not currently supported in 64 bit operations. For example, to use a zero value in a 64 bit comparison, you must pass in the zero value as a parameter. ```c [[clang::fhe_program]] void greater_than_zero( [[clang::encrypted]] uint64_t value, [[clang::encrypted]] uint64_t zero, [[clang::encrypted]] uint64_t *result ) { *result = value > zero; } ``` While addition, subtraction, and multiplication are all supported, division by plaintext and ciphertext is not yet available. ## Branching operations Branching over plaintext values works as expected. However, it is a fundamental limitation of FHE that generic branching over ciphertext values is not supported. This is because the program would need to change it's executation path based on encrypted data, which would reveal information about the encrypted data. Hence it is not possible to have `if` statements or loops that depend on ciphertext values. However, we do support a limited form of branching known as `select` (or `iselect` for signed values). This is essentially a ternary operator that allows you to choose between two values based on a condition. The syntax is `result = select(cond, true_value, false_value)`, where `cond`, `true_value`, and `false_value` can all be any encryptedness (plaintext or ciphertext). This allows you to implement branching logic without revealing information about the encrypted data. ## Result outputting This restriction applies to writing FHE programs that are uploaded to the current version of SPF. If the C function has a return value (e.g. `uint32_t`), it will be ignored so instead you should use a pointer argument to "receive" the return value instead. This should be a dedicated "output" parameter in the form of a pointer, because pointer parameter used for input will have the changes also ignored. For example, if you have a sorting program that takes in an array (in the form of pointer) and sort it "in place", you won't get result out of the function; you must copy it to another pointer argument that has the same space allocated and is dedicated to receive the sorted array as an output. This is not a fundamental limitation and we are working to get this restriction removed soon. ## WASM Today, we have limited support for WebAssembly. You can see an example of how to wrap the client functionality in WASM [here](https://github.com/Sunscreen-tech/parasol_wasm_demo). --- ## Interacting with SPF in a smart contract To facilitate interaction with SPF in Web3, we have developed a smart contract library that is [open source](https://github.com/Sunscreen-tech/sunscreen-contracts) that includes basic building blocks to build dApps. You've already seen some of them in the [example](example3.md). Here we show how to use them in depth. --- ## Comparisons, Branching, and Loops Programs are often written with control flow structures such as conditionals and loops in order to repeat actions or change the flow of execution based on certain conditions. Here will will go into what control flow mechanisms can be expressed in an FHE program. ## Comparisons We support the common comparison operators between plaintext and encrypted values: `<`, `>`, `<=`, `>=`, `==`, and `!=`. ```c [[clang::fhe_program]] void compare( [[clang::encrypted]] uint32_t a, [[clang::encrypted]] uint32_t b, [[clang::encrypted]] bool *result_lt, [[clang::encrypted]] bool *result_gt, [[clang::encrypted]] bool *result_le, [[clang::encrypted]] bool *result_ge, [[clang::encrypted]] bool *result_eq, [[clang::encrypted]] bool *result_ne ) { *result_lt = (a < b); *result_gt = (a > b); *result_le = (a <= b); *result_ge = (a >= b); *result_eq = (a == b); *result_ne = (a != b); } ``` ## Branching and the `select`/`iselect` operation We do not support `if` statements in FHE programs at the moment. However, we do support choosing between two different values based on a condition using the `selectX` (unsigned) and `iselectX` (signed) function, where `X` is the bit width of the values being selected. This enables writing more complex logic in FHE programs. ```c [[clang::fhe_program]] void max( [[clang::encrypted]] uint8_t a, [[clang::encrypted]] uint8_t b, [[clang::encrypted]] uint8_t *result ) { // Use the ternary operator to select the maximum value *result = select8((a > b), a, b); } ``` ## Loops Loops where the loop condition is in plaintext are supported in FHE programs, which includes both `while` loops and `for` loops. ```c [[clang::fhe_program]] void sum( [[clang::encrypted]] uint32_t* a, uint32_t len, [[clang::encrypted]] uint8_t *result ) { uint32_t x = 0; for (uint32_t i = 0; i < len; i++) { x += a[i]; } *result = x; } ``` --- ## Preliminary knowledge This section will help in understanding some of the terminology we use in SPF. Note that it is a brief introduction and we do have a [more comprehensive section](fhe.md) for those interested in learning more. The knowledge provided here is primarily related to cryptography; we assume readers are already familiar with the C programming language and (for web3) Solidity. FHE is the next generation of public key cryptography. In addition to being able to encrypt and decrypt data, you can perform arithmetic, logical, and comparison operations directly over the encrypted data. This enables applications such as matching algorithms over an encrypted order book, sorting an encrypted list, and more. Please note this section is intended to provide high level details and intuition, not rigorous technical definitions. If you're interested in learning more about the details, we highly recommend [this textbook](https://link.springer.com/book/10.1007/978-1-4939-1711-2) as an introduction to cryptography. ## Cryptography basics Here are some key concepts to understand before diving into FHE: - **Plaintext vs. ciphertext**: Plaintext refers to unencrypted data (i.e. data in the clear) whereas ciphertext refers to encrypted data. - **Public key vs. private key**: In public key cryptography, there are two types of keys. The public key only allows for data to be encrypted. The public key can be shared with anyone. In FHE we have an additional type of public key known as a compute key, which enables computations to be performed on ciphertexts. The private key is kept secret, as it allows for data to be decrypted (and encrypted too). - **"Homomorphic"**: We use this term to denote computations performed directly on encrypted data. For example, if we say "homomorphic addition," we are referring to the operation that adds the underlying plaintexts of two ciphertexts without decrypting them first. ## Threshold FHE computing model The SPF supports applications where _multiple_ users' private data can be used as program inputs (e.g. in an auction, voting). For these apps, all parties will use the same public key to encrypt their data. To keep the private key secure while still enabling decryption, the private key is sharded into pieces (aka shares) and held by a semi-trusted "threshold committee" as part of the SPF infrastructure using a technique known as _threshold FHE_. A threshold set of committee members must come together to decrypt any ciphertext in the system. This ensures no single committee member ever has access to the private key, and that the entirety of the private key is never revealed to anyone. For more privacy-conscious users, we also support re-encryption. This technique enables users to keep ciphertexts decryptable under their own private key rather than the threshold committee key. ## FHE vs other privacy-preserving technologies If you're wondering how FHE differs from other privacy-preserving technologies, some nice things about FHE include: - The computation itself is non-interactive, so it's great for decentralized settings where parties may be in very different locations around the world. - FHE is quite "fault-tolerant" from a developer's standpoint, e.g. FHE is not susceptible to the broad range of attacks trusted execution environments are susceptible to. - FHE allows you to combine your private data with _others'_ private data in apps, unlike many zero-knowledge proof systems. - FHE is post-quantum, meaning FHE is secure against attacks from a quantum computer. An alternative way of thinking about this property is that FHE is "future-proof," even in the case of harvest now, decrypt later attacks. --- ## Reencryption At a high level, reencryption in SPF transforms a ciphertext decryptable with respect to the committee's private key to a ciphertext decryptable by a particular user's private key. What's magical about reencryption is that we can accomplish this transformation without ever decrypting the ciphertext! Reencryption is useful for users who worry about the possibility that a majority of the threshold committee may collude to decrypt network data that aren't supposed to. Today, our implementation relies on one-time pads. Currently, re-encryption is only available for web2 programs. {{#include snippets/init-required.md}} ## How reencryption works The standard decryption workflow requires a network round-trip to the SPF threshold committee: 1. Request threshold decryption 2. Wait for committee to decrypt 3. Receive plaintext result With reencryption, the final decryption happens locally: 1. Generate one-time pad (OTP) keypair 2. Request reencryption with public OTP 3. Request threshold decryption of reencrypted ciphertext 4. Locally decrypt with secret OTP (instant) ## Generating OTP keypair To generate a one-time pad keypair, use the `generateOtp()` function: ```typescript {{#include ../web2-examples/src/recrypt.ts:generate_otp}} ``` The function generates a keypair where the public OTP is FHE-encrypted and sent to SPF for reencryption, while the secret OTP is kept locally for final decryption. **Returns**: `Promise` - Object with `publicOtp` and `secretOtp` (both `Uint8Array`) **Security note**: The secret OTP must remain private. Only share the public OTP with SPF. ## Requesting reencryption To request reencryption of a ciphertext using a one-time pad, use the `requestReencryption()` function: ```typescript {{#include ../web2-examples/src/recrypt.ts:request_reencryption}} ``` This submits a reencryption request to SPF. The service reencrypts the ciphertext under the provided public OTP, enabling local decryption with the corresponding secret OTP. The signer must have decrypt access to the ciphertext. **Parameters**: - `signer`: `AnySigner` - Signer for authentication (must have decrypt access) - `ciphertextId`: `CiphertextId` - The ciphertext identifier to reencrypt - `publicOtp`: `Uint8Array` - The public one-time pad bytes from `generateOtp()` **Returns**: `Promise` - The reencryption handle (branded hex string with `0x` prefix) ## Checking reencryption status To check the status of a reencryption request, use the `checkReencryptionStatus()` function: ```typescript const status = await spf.checkReencryptionStatus(reencryptHandle); if (status.status === "success") { console.log("Reencrypted ciphertext ID:", status.payload.id); } else if (status.status === "failed") { console.error("Reencryption failed:", status.payload?.message); } ``` **Parameters**: - `reencryptHandle`: `ReencryptHandle` - The reencryption handle from `requestReencryption()` **Returns**: `Promise` - Discriminated union with `status` field ## Waiting for reencryption completion For convenience, use `waitForReencryption()` to automatically poll until reencryption completes: ```typescript {{#include ../web2-examples/src/recrypt.ts:wait_for_reencryption}} ``` This function polls `checkReencryptionStatus()` until reencryption succeeds or fails, then returns the reencrypted ciphertext ID. **Parameters**: - `reencryptHandle`: `ReencryptHandle` - The reencryption handle from `requestReencryption()` - `signal`: `AbortSignal` - Optional cancellation signal (use `AbortSignal.timeout(ms)` for timeout) **Returns**: `Promise` - The reencrypted ciphertext identifier ### Cancellation support The `waitForReencryption()` function supports cancellation via `AbortSignal`: ```typescript // Wait with 30 second timeout const reencryptedCtId = await spf.waitForReencryption( reencryptHandle, AbortSignal.timeout(30000), ); ``` ## Local OTP decryption After reencryption completes, use the secret OTP to decrypt locally: ```typescript {{#include ../web2-examples/src/recrypt.ts:otp_decrypt}} ``` This workflow performs threshold decryption on the reencrypted ciphertext, retrieves the OTP-encrypted polynomial bytes, and locally decrypts using the secret OTP. **Key functions**: - `requestDecryption()`: Request threshold decryption of the reencrypted ciphertext (returns `DecryptHandle`) - `getPolynomialBytesForOtp()`: Wait for threshold decryption and retrieve OTP-encrypted polynomial bytes (not parsed plaintext) - `otpDecrypt()`: Perform instant local decryption using the secret OTP **`getPolynomialBytesForOtp()` parameters**: - `decryptHandle`: `DecryptHandle` - The decrypt handle from `requestDecryption()` - `signal`: `AbortSignal` - Optional cancellation signal (use `AbortSignal.timeout(ms)` for timeout) **`otpDecrypt()` parameters**: - `polyBytes`: `Uint8Array` - The OTP-encrypted polynomial bytes from `getPolynomialBytesForOtp()` - `secretOtp`: `Uint8Array` - The secret one-time pad bytes from `generateOtp()` - `bitWidth`: `8 | 16 | 32 | 64` - The bit width of the encrypted value - `signed`: `boolean` - Whether the value is signed (default: false) **Returns**: `Promise` - The decrypted plaintext value ## Complete reencryption workflow Here is a complete example demonstrating the entire reencryption workflow: ```typescript {{#include ../web2-examples/src/recrypt.ts:complete_reencryption}} ``` This workflow: 1. Generates an OTP keypair 2. Requests reencryption with the public OTP 3. Waits for reencryption to complete 4. Requests threshold decryption of the reencrypted ciphertext 5. Retrieves OTP-encrypted polynomial bytes 6. Performs instant local decryption with the secret OTP --- ## Requesting program runs Once programs are uploaded and users have provided encrypted inputs, program runs can be requested from SPF. This section covers parameter encoding, run submission, status checking, and result retrieval using the TypeScript client library. {{#include snippets/init-required.md}} ## Encoding parameters Before submitting a run, parameters must be encoded to match the FHE program's signature. For example, the `tally_votes` program has the following signature: ```c,ignore void tally_votes([[clang::encrypted]] int8_t *votes, uint16_t num_votes, [[clang::encrypted]] bool *didTheIssuePass) ``` The TypeScript encoding maps directly to this C signature: ```typescript {{#include ../web2-examples/src/run.ts:create_parameters}} ``` ### Parameter types The client library provides helper functions for each parameter type: **Ciphertext array parameter**: For an array of encrypted inputs (C type `[[clang::encrypted]] (u)intN_t *value`) ```typescript const param = spf.createCiphertextArrayParameter([ spf.asCiphertextId(ctId1), spf.asCiphertextId(ctId2), spf.asCiphertextId(ctId3), ]); ``` **Single ciphertext parameter**: For a single encrypted input (C type `[[clang::encrypted]] (u)intN_t value`) ```typescript const param = spf.createCiphertextParameter(spf.asCiphertextId(ciphertextId)); ``` **Plaintext parameter**: For a plaintext input (C type `(u)intN_t value`) ```typescript const param = spf.createPlaintextParameter(16, 123); ``` **Plaintext array parameter**: For an array of plaintext inputs (C type `(u)intN_t *value`) ```typescript const param = spf.createPlaintextArrayParameter(16, [1, 2, 3, 4]); ``` **Output ciphertext array parameter**: For an array of encrypted outputs (C type `[[clang::encrypted]] (u)intN_t *value`) ```typescript // Create an output array for 1 encrypted boolean (8-bit) const param = spf.createOutputCiphertextArrayParameter(8, 1); // Create an output array for 3 encrypted 16-bit values const param = spf.createOutputCiphertextArrayParameter(16, 3); ``` ## Submitting a program run To submit a run request to execute an FHE program, use the `submitRun()` function: ```typescript {{#include ../web2-examples/src/run.ts:submit_run}} ``` The `submitRun()` function submits the computation request to SPF and returns a unique run handle. This handle is used to check status and retrieve results. **Parameters**: - `signer`: `AnySigner` - Signer for authentication. The signer must have the access to use all the input ciphertexts (i.e. have been granted `run` access to each ciphertext). - `libraryId`: `LibraryId` - The library identifier from program upload (use `spf.asLibraryId(string)` to create) - `programName`: `ProgramName` - The program entry point name (use `spf.asProgramName(string)` to create) - `parametersWithAuth`: `SpfParameterWithAuth[]` - Array of parameters with authentication data (returned from parameter creation functions like `createCiphertextArrayParameter()`) **Returns**: `Promise` - The run handle (branded hex string with `0x` prefix) ## Checking run status To check the status of an FHE program run, use the `checkRunStatus()` function: ```typescript {{#include ../web2-examples/src/run.ts:check_status}} ``` The function returns a discriminated union with the current status. Use TypeScript's type narrowing to safely access status-specific fields. **Parameters**: - `runHandle`: `RunHandle` - The run handle from `submitRun()` **Returns**: `Promise` - Discriminated union with `status` field ## Waiting for run completion For convenience, use `waitForRun()` to automatically poll until the run completes: ```typescript {{#include ../web2-examples/src/run.ts:wait_for_run}} ``` This function polls `checkRunStatus()` until the run succeeds or fails, then returns the final status. **Parameters**: - `runHandle`: `RunHandle` - The run handle from `submitRun()` - `signal`: `AbortSignal` - Optional cancellation signal (use `AbortSignal.timeout(ms)` for timeout) **Returns**: `Promise` - Final run status ### Cancellation support The `waitForRun()` function supports cancellation via `AbortSignal`: ```typescript {{#include ../web2-examples/src/run.ts:abort_signal}} ``` This enables timeout control and the ability to cancel long-running operations. ## Deriving result ciphertext identifiers After a program run completes, the output ciphertext IDs must be derived using the `deriveResultCiphertextId()` function: ```typescript {{#include ../web2-examples/src/run.ts:derive_result}} ``` This function deterministically computes the ciphertext ID for a program output based on the run handle and output index. **Parameters**: - `runHandle`: `RunHandle` - The run handle from `submitRun()` - `outputIndex`: `number` - The index of the output ciphertext (0-255). **Returns**: `CiphertextId` - The result ciphertext identifier (branded hex string with `0x` prefix) ### Determining the output index The output index corresponds to the position in the flattened array of all output ciphertexts from all output parameters. For example, if a program has these parameters: ```c,ignore void example( int16_t* encrypted_inputs, // Input (ignored for index) uint16_t num_inputs, // Plaintext (ignored for index) int16_t* first_output, // Output array with 2 elements uint8_t* second_output // Output array with 1 element ) ``` The parameters would be: ```typescript const parameters = [ spf.createCiphertextArrayParameter([...]), spf.createPlaintextParameter(16, 5), spf.createOutputCiphertextArrayParameter(16, 2), // Indices 0, 1 spf.createOutputCiphertextArrayParameter(8, 1), // Index 2 ]; ``` Output indices: - Index 0: first element in `first_output` - Index 1: second element in `first_output` - Index 2: `second_output` --- ## Requesting program runs(Docs) Once programs are uploaded and users have provided encrypted inputs, program runs can be requested from SPF. This section covers parameter encoding, run submission, and result retrieval using Solidity smart contracts. **Important**: Ensure programs are [uploaded](upload3.md#program-upload) before requesting runs. ## Encoding parameters Before submitting a run, parameters must be encoded to match the FHE program's signature. The Solidity library provides helper functions for each parameter type that handle the encoding internally. For all functions below the underlying data in the ciphertext must be the correct bit width matching the C program signature (8, 16, 32 or 64 bits). ### Parameter types The following functions create properly encoded parameters for different C types: **Single ciphertext parameter**: For a single encrypted input (C type `[[clang::encrypted]] (u)intN_t value`) The function signature is ```solidity function createCiphertextParameter(SpfCiphertextIdentifier identifier) returns (SpfParameter memory) ``` **Ciphertext array parameter**: For an array of encrypted inputs (C type `[[clang::encrypted]] (u)intN_t *value`) The function signature is ```solidity function createCiphertextArrayParameter(SpfCiphertextIdentifier[] memory identifiers) returns (SpfParameter memory) ``` **Plaintext parameter**: For a plaintext input (C type `(u)intN_t value`) The function signature is ```solidity function createPlaintextParameter(uint8 bitWidth, int128 value) returns (SpfParameter memory) ``` **Plaintext array parameter**: For an array of plaintext inputs (C type `(u)intN_t *value`) The function signature is ```solidity function createPlaintextArrayParameter(uint8 bitWidth, int128[] memory values) returns (SpfParameter memory) ``` **Output ciphertext array parameter**: For an array of encrypted outputs (C type `[[clang::encrypted]] (u)intN_t *value`) The function signatures are ```solidity function createOutputCiphertextParameter(uint8 bitWidth) returns (SpfParameter memory) ``` or ```solidity function createOutputCiphertextArrayParameter(uint8 bitWidth, uint8 numElements) returns (SpfParameter memory) ``` For array outputs, `numElements` specifies the number of ciphertexts in the output array and is required to derive output handles later. ## Submitting a program run To submit a run request to execute an FHE program, use the `requestRunAsContract()` function: ```solidity function requestRunAsContract(SpfLibrary spfLibrary, SpfProgram program, SpfParameter[] memory params) returns (SpfRunHandle) ``` The function submits the computation request to SPF and returns a unique run handle. This handle is used to derive output ciphertext identifiers. **Parameters**: - `spfLibrary`: `SpfLibrary` - The library identifier from [program upload](upload3.md#program-upload) - `program`: `SpfProgram` - The program entry point name (function name in C code) - `params`: `SpfParameter[] memory` - Array of encoded parameters in order matching the C function signature **Returns**: `SpfRunHandle` - The run handle for deriving output ciphertexts ## Retrieving output ciphertexts After a program run completes, output ciphertexts IDs can be calculated using the `getOutputHandle()` function based on the run handle and output index. ```solidity function getOutputHandle(SpfRunHandle runHandle, uint8 index) returns (SpfParameter memory) ``` **Parameters**: - `runHandle`: `SpfRunHandle` - The run handle from `requestRunAsContract()` - `index`: `uint8` - The index of the output ciphertext (0-255) **Returns**: `SpfParameter memory` - The result ciphertext as an `SpfParameter`, which can be used directly as input to another program or converted to a raw ciphertext identifier for [decryption](decrypt3.md#converting-output-parameters) ### Determining the output index The output index corresponds to the position in the flattened array of all output ciphertexts from all output parameters. For example, if a program has these parameters: ```c,ignore void example( int16_t* encrypted_inputs, // Input (ignored for index) uint16_t num_inputs, // Plaintext (ignored for index) int16_t* first_output, // Output array with 2 elements uint8_t* second_output // Output array with 1 element ) ``` The Solidity parameters would be: ```solidity SpfParameter[] memory parameters = new SpfParameter[](4); parameters[0] = Spf.createCiphertextArrayParameter([...]); parameters[1] = Spf.createPlaintextParameter(16, 5); parameters[2] = Spf.createOutputCiphertextArrayParameter(16, 2); // Indices 0, 1 parameters[3] = Spf.createOutputCiphertextArrayParameter(8, 1); // Index 2 ``` Output indices: - Index 0: first element in `first_output` - Index 1: second element in `first_output` - Index 2: `second_output` ## Built-in trivial ciphertexts As a convenience, the SPF library provides access to known encryptions of 0 and 1 using the `createTrivialZeroCiphertextParameter(uint8 bitWidth)` and `createTrivialOneCiphertextParameter(uint8 bitWidth)` functions. ## Verifying ciphertext access To ensure that a program will run (which requires all the ciphertexts inputs are valid and available to the program), applications should verify ciphertext access before submitting a run request. However, smart contracts cannot directly verify the status of ciphertexts stored in the SPF service. To solve this problem, the SPF service provides off-chain access verification functionality that can be used to obtain a signature confirming the SPF has access to specific ciphertexts with the correct permissions. Smart contracts can then verify these signatures on-chain before executing the program run, ensuring that their requests will execute successfully. See the [tooling for acquiring these signatures](acl3.md#obtain-access-confirmation) and [verifying access control](acl3.md#verify-ciphertext-access) for more details. --- ## Using SPF in web2 This section covers using Sunscreen's Secure Processing Framework (SPF) in web2 applications via the TypeScript client library. The `@sunscreen/spf-client` package provides a comprehensive API for interacting with SPF. The client handles FHE encryption, program execution, threshold decryption, and access control management through a straightforward TypeScript interface. ## What the TypeScript client provides The SPF client library enables: - **Program management**: Upload and download compiled FHE programs - **Encryption**: Client-side encryption of values and arrays - **Ciphertext management**: Upload, download, and manage encrypted data - **Program execution**: Submit FHE computations with encrypted inputs - **Threshold decryption**: Request collaborative decryption from the threshold network - **Re-encryption**: Use one-time pads for instant local decryption - **Access control**: Manage permissions for running programs and decrypting results ## Architecture overview The TypeScript client communicates with SPF via REST API endpoints. All sensitive operations require authentication using ECDSA signatures (via `PrivateKeySigner` or ethers.js signers). Encryption and decryption operations use WebAssembly modules for performance. The library works in both Node.js and browser environments with identical APIs. ## Getting started See the [Installation and initialization](init.md) page to begin using the SPF TypeScript client. --- ## Init Required Ensure the client is [initialized](init.md#initialization) before using these functions. --- ## Quick start If you're short on time, this section covers what's needed to start working with SPF quickly. We'll walk through an end-to-end encrypted voting example in web2 and web3 -- starting with writing the voting program code (along with its corresponding smart contract in the web3 case), before moving on to how to deploy them and request program runs and decryptions. --- ## Functions and Types If you've read our Parasol docs regarding functions and types, this section will be content you're already familiar with. ## Types and type conversions We currently support signed and unsigned integers up to 64 bits and booleans using the standard names (i.e `uint16_t` for an unsigned 16 bit value, `int32_t` for a signed 32 bit value, `bool` for booleans, etc). These are defined in the `parasol.h` header file. Programs support converting between different integer sizes. When converting from a smaller integer to a larger one, the higher bits are padded and therefore the value does not change. However, when converting from a larger integer to a smaller one, the higher bits are discarded, leading to a different value. Here is an example: ```c [[clang::fhe_program]] void size_conversions( [[clang::encrypted]] uint8_t u8_a, [[clang::encrypted]] uint32_t u32_b, [[clang::encrypted]] uint32_t *u32_output, [[clang::encrypted]] uint8_t *u8_output ) { // Convert to a larger integer uint32_t u32_a = u8_a; *u32_output = u32_a + u32_b; // Truncate to a smaller integer uint8_t u8_b = u32_b; *u8_output = u8_a + u8_b; } ``` ## Function parameter annotations FHE programs can be run with either plaintext or ciphertext arguments. In order to specify what argument is of what type, you can use the `[[clang::encrypted]]` annotation before a function's parameter. ```c [[clang::fhe_program]] void func( uint32_t a, [[clang::encrypted]] uint32_t b, [[clang::encrypted]] uint32_t *result ) { // do something with a and b and store to result } ``` In this particular example function, the first parameter is expected to be a plaintext value, while the second parameter is expected to be an encrypted value. When the program is run, the parameters passed to the function should match the annotation given at the time of compilation. ## Inlining One of the tenets of making maintainable code is developing smaller functions that can be reused in several places, increasing the readability of a program. For our FHE programs, functions marked as `inline` can be used to reduce common/boilerplate code in the program, making the final code easier to reason about and test. The following example demonstrates this modularity. It is a program that calculates the price of some item given its base price and a packed byte representing the discounts a user could have applied to their purchase. The `get_bit` function is used several times to extract the specific discounts from the `discounts_flags` argument. These discounts are then used to calculate the final price of the item. Inlining here enables us to follow DRY principles and reduce code duplication. ```c inline bool get_bit(uint32_t flags, unsigned int n) { return ((flags >> n) & 0x1); } [[clang::fhe_program]] void price_item( uint32_t base_price, [[clang::encrypted]] uint32_t discounts_flags, [[clang::encrypted]] uint32_t *final_price ) { bool has_store_membership_discount = get_bit(discounts_flags, 0); bool has_coupon_discount = get_bit(discounts_flags, 1); uint32_t price = base_price; // Discount the items based on the discounts the customer has price = select32(has_store_membership_discount, price - 2, price); price = select32(has_coupon_discount, price - 3, price); *final_price = price; } ``` --- ## Uploading programs and ciphertexts Before interacting with an FHE application, the program must be uploaded to SPF and user inputs must be encrypted and stored. This section covers uploading programs, encrypting values, and uploading ciphertexts using the TypeScript client library. {{#include snippets/init-required.md}} ## Type safety with branded types The SPF client uses TypeScript branded types to provide compile-time type safety for identifiers and handles. These types are hex strings with a `0x` prefix, but TypeScript distinguishes between different kinds of identifiers: - `LibraryId` - Identifies uploaded FHE programs - `CiphertextId` - Identifies uploaded ciphertexts - `RunHandle` - Identifies program execution runs - `DecryptHandle` - Identifies decryption requests These branded types work transparently when passing values between functions, but prevent accidentally mixing up different types of identifiers (for example, using a ciphertext ID where a library ID is expected). The types are compatible with regular hex strings, so you can store them as strings and use them directly. ## Program upload To upload a compiled FHE program to SPF, use the `uploadProgram()` function: ```typescript {{#include ../web2-examples/src/upload.ts:upload_program}} ``` The `uploadProgram()` function uploads the compiled program binary to SPF and returns a unique library identifier. This library ID is used when submitting program runs. **Parameters**: - `programBytes`: `Uint8Array` - The compiled FHE program binary **Returns**: `Promise` - The library identifier (branded hex string with `0x` prefix) ## Encrypting values To encrypt values for use as program inputs, use the `encryptValue()` function: ```typescript {{#include ../web2-examples/src/upload.ts:encrypt_value}} ``` **Parameters**: - `value`: `number | bigint` - The plaintext value to encrypt - `bitWidth`: `8 | 16 | 32 | 64` - The bit width **Returns**: `Promise` - The encrypted ciphertext bytes ## Ciphertext upload To upload ciphertexts to SPF, use the `uploadCiphertext()` function: ```typescript {{#include ../web2-examples/src/upload.ts:upload_ciphertext}} ``` The `uploadCiphertext()` function uploads encrypted data to SPF and returns a unique ciphertext identifier (a keccak256 hash of the ciphertext bytes). The signer is used for authentication, and the uploader becomes the initial admin of the ciphertext. **Parameters**: - `signer`: `AnySigner` - Signer for authentication - `ciphertextBytes`: `Uint8Array` - The encrypted ciphertext bytes **Returns**: `Promise` - The ciphertext identifier (branded hex string with `0x` prefix) ## Complete example Here is a complete example that encrypts a value, uploads it, and grants access: ```typescript {{#include ../web2-examples/src/upload.ts:complete_upload_example}} ``` --- ## Uploading programs and ciphertexts(Docs) Before anyone can use your FHE app, the SPF must be able to access he FHE program and any required ciphertexts. Unfortunately, storing FHE programs and their encrypted inputs on-chain will be extremely expensive (and in many cases not even possible due to how large FHE ciphertexts are). To get around this challenge, SPF provides off-chain data storage for your FHE program and any accompanying inputs. This makes it cost effective to use FHE programs in web3 as well as making it straightforward for the SPF to operate as a service. In this section, we'll look at how to upload programs and user inputs to SPF. ## Program upload To upload a compiled FHE program to SPF, use the `upload-program` command: ```sh spf-client upload-program --file ``` The command uploads the compiled program binary to SPF and returns a unique library identifier for use in program run requests. **Parameters**: - `--file `: Path to the compiled program library file (required) **Returns**: Library identifier that uniquely identifies the uploaded program ## Encrypting values To encrypt values for use as program inputs, use the `generate-ciphertext` command: ```sh spf-client generate-ciphertext --value --bits [--output ] [--upload --private-key ] ``` The command encrypts a plaintext value and either saves it to a file or uploads it directly to SPF. **Parameters**: - `--value `: The plaintext value to encrypt (required) - `--bits `: The bit width (8, 16, 32, or 64) (required) - `--output `: Path to save the ciphertext file (optional, used when not uploading) - `--upload`: Upload the ciphertext to SPF after generation (optional, requires `--private-key`) - `--private-key `: User's private key in hexadecimal format (required with `--upload`) **Returns**: - Without `--upload`: Saves the encrypted ciphertext to the specified output file - With `--upload`: Ciphertext identifier for the uploaded ciphertext ## Ciphertext upload To upload ciphertexts stored on disk to SPF, use the `upload-ciphertext` command: ```sh spf-client upload-ciphertext --file --private-key ``` The command uploads encrypted data to SPF and returns a unique ciphertext identifier (a keccak256 hash of the ciphertext bytes). The private key is used for authentication, and the uploader becomes the initial admin of the ciphertext. **Parameters**: - `--file `: Path to the ciphertext file to upload (required) - `--private-key `: User's private key in hexadecimal format (required) **Returns**: Ciphertext identifier for use in program run requests --- ## Who should use the Secure Processing Framework? Our SPF is designed to help _any_ engineer (web2 or web3) bring privacy to their application, without having to run additional infrastructure or become a cryptography expert. Furthermore, our goal is to support deployment of FHE-enabled programs directly to major chains. No L2, rollup, or new chain needed. As proof of concept, we currently support programs on Ethereum Sepolia and Monad (testnet). We'll be integrating new chains (in testnet mode) soon. We want to make our product as developer friendly as possible. If you run into any issues or need specific features, don't hesitate to [send us a message](mailto:ravital@sunscreen.tech). ## You're a blockchain developer excited to build your latest dApp. We believe that you should be able to deploy to any chain you desire; accordingly, our SPF enables doing exactly that. Our unique architecture allows you to experiment with deploying to different chains, without having to rewrite your FHE program. To help you get started, we provide some [starter code](example3.md) to assist with this process. ## You're a web2 developer excited to build one of your first dApps. Creating end-to-end applications in a smart contract programming language like Solidity can be challenging. Current restrictions on program operations are temporary (see [limitations](limits.md)). Our goal is to enable developers to transform existing application code into privacy-preserving applications by simply adding annotations to indicate what data should be kept private. The SPF architecture supports experimenting with different blockchains without rewriting application code. This allows you to write your application once and use it on _any_ chain. ## You're a web2 engineer excited to bring privacy to your product. Our offering is designed to be full service -- handling storage, permissioning, and key management on your behalf. However, we have the flexibility to integrate with your existing storage provider or allow you to handle key management and permissioning if desired. We know from experience how painful it is to have to rewrite application code; the SPF allows you to bring your existing C code (with some restrictions today) and transform it into privacy-preserving code by adding some annotations. --- ## Developer workflow This section describes the process of creating, deploying, and running programs with SPF in both web2 and web3 environments. The developer experience is largely similar across both ecosystems, with the primary differences being how program runs are initiated and how results are decrypted. This section covers the high level workflow, while detailed information about SPF service endpoints and the smart contract library is provided in later sections. - [Creating your program](#creating-your-program) - [Providing program inputs](#providing-program-inputs) - [Running your web2 program](#running-your-web2-program) - [Running your web3 program](#running-your-web3-program) ## Creating your program The initial program creation process is identical for both web2 and web3 applications. The workflow consists of: - **Write the FHE program in C.** C code can be written as normal, with a few specific patterns used to handle the compute model of FHE. More details are provided in [the writing section](writing.md). - **Compile the FHE program library to binary.** [The compiled program](build.md) is packaged as an ELF binary file for use by SPF. - **Upload the program to SPF.** The provided client uploads the program library and returns an identifier for referencing the program. ## Providing program inputs Developers or end users can: - **Generate ciphertext inputs.** The user uploads an encrypted value to the SPF service. The service stores this value and returns an identifier for referencing the ciphertext. ## Running your web2 program In web2 environments, applications integrate with the SPF service through REST API endpoints. The two most useful endpoints for web2 use cases are: 1. **Request program run.** Programs are executed through a run endpoint with the specified FHE program and the encrypted inputs. The endpoint returns a unique identifier for the computation, which can be used to track status and derive the resulting ciphertexts. 2. **Request decryption.** Once the computation completes, results can be decrypted by calling the SPF decryption endpoint. The service returns a decryption handle for tracking status and retrieving the result. ## Running your web3 program In web3 environments, applications interact with SPF service endpoints through the SPF data bus service, which monitors a blockchain for SPF related events. Developers write smart contracts that use the provided smart contract library, which offers two main functionalities: 1. **Request programs run.** The run request function prepares input data and initiates a run request. This emits a run request event that the data bus (aka a pull oracle) observes and routes to SPF for execution. The function also returns a unique identifier for the computation, which can be used to track status off-chain and derive the resulting ciphertexts. 2. **Request decryption.** Once the computation completes, the decryption request functionality initiates decryption for a ciphertext identifier. This emits a decryption request event that the data bus (aka pull oracle) observes and routes to SPF for decryption. Upon completion, SPF posts the result back on-chain using the specified callback. ### What is done on-chain vs off-chain? The SPF design enables developers to bring FHE directly to applications on any chain without requiring migration to a new chain, L2, or rollup. This architecture maintains a similar developer workflow for both web2 and web3 environments, as most blockchains are not designed to handle FHE operations directly on-chain. Summary of on-chain vs off-chain operations: - FHE programs (the binary bytes) are stored _off-chain_. However, program identifiers are stored _on-chain_ as part of the contract. - Ciphertexts (encrypted inputs and results) are stored _off-chain_. However, ciphertext identifiers are typically stored _on-chain_. - FHE computation is performed _off-chain_. However, the contract code that requests program runs is stored _on-chain_. - Ciphertext decryption is performed _off-chain_ via the threshold committee, which also operates off-chain. However, the contract code that requests decryption is stored _on-chain_. --- ## Writing an FHE program In this section, we walk through how to create an FHE program that can be used with the Secure Processing Framework (SPF). FHE programs are written in C. If you have previous experience working with our Parasol compiler, most of this section will be content you're already familiar with. Please note that there are some common conventions that must be adhered to for the SPF to run an FHE program (notably for [result outputting](limits.md#result-outputting)).