Skip to main content

Overview

This guide outlines key best practices for developing with CoFHE, based on recommendations from our development team. Following these practices will help you build secure, efficient, and maintainable FHE-enabled smart contracts.

Prerequisites

Before reading this guide, you should:
  • Have completed the Quick Start setup
  • Understand basic FHE concepts and encrypted types
  • Be familiar with Solidity smart contract development

Security Considerations

Publish Decrypted Data Carefully

Decryption is a multi-step process: the client requests a plaintext + signature off-chain via decryptForTx, then publishes or verifies the result on-chain using FHE.publishDecryptResult or FHE.verifyDecryptResult. Once published, the plaintext is visible to everyone on the blockchain. Key principles:
  • Evaluate information leakage: Before publishing a decrypted value on-chain, consider what information you’re exposing and what an observer might learn from it
  • Minimize published values: Only publish decrypted results when your protocol truly requires the plaintext on-chain. Use decryptForView if you only need to display the value in a UI
  • Use verifyDecryptResult when possible: If your contract only needs to confirm a value without storing it publicly, prefer FHE.verifyDecryptResult over FHE.publishDecryptResult
Publishing a decrypted value on-chain makes it permanently visible to all observers. Always consider whether you need decryptForTx (on-chain proof) or decryptForView (UI-only) for your use case.

Always update permissions

Remember to call FHE.allowThis() after modifying any encrypted value that needs to be accessed later:
counter = FHE.add(counter, FHE.asEuint32(1));
FHE.allowThis(counter);
Without calling FHE.allowThis(), your contract won’t be able to access the encrypted value in subsequent operations. This is a common source of errors in FHE development.

Avoid code branching based on encrypted data

Remember: there is no secure code branching with FHE. Decrypting to make branching decisions is generally a bad practice and can leak information. Best practices:
  • Use constant-time algorithms: Design your code to follow the same execution path regardless of encrypted values
  • Prefer FHE.select over conditional logic: Use built-in selection operations rather than decrypting for if/else decisions
Example:
// Don't reveal encrypted values to make branching decisions
FHE.publishDecryptResult(condition, plaintext, signature);
if (plaintext > 0) {
    result = a;
} else {
    result = b;
}
Since conditional branching doesn’t work with encrypted values, always use FHE.select() for conditional logic. This ensures your code follows a constant execution path regardless of the encrypted values.

Performance Optimization

Optimize computational efficiency

FHE operations are computationally expensive. Optimize your contracts to minimize overhead: Key strategies:
  • Minimize FHE operations: Each operation adds computational overhead, so reduce the number of operations where possible
  • Use the minimum bit-width necessary: Choose the smallest integer type that can safely represent your data
euint64 counter;  // Using 64 bits when 32 would suffice

Reuse Encrypted Constants

Encrypt constant values once and reuse them to save gas:
// Good practice: Encrypt once, reuse many times
euint32 ONE = FHE.asEuint32(1);
FHE.allowThis(ONE);

// Later in the code
counter = FHE.add(counter, ONE);
counter = FHE.add(counter, ONE);  // Reusing the same encrypted constant
Reusing encrypted constants is a gas optimization technique. Encrypt frequently used values (like 0, 1, or common thresholds) once at contract initialization and reuse them throughout your contract’s lifetime.

Plan for Asynchronous Operations

CoFHE operations are asynchronous by nature. Design your application to handle this gracefully: UI considerations:
  • Implement loading indicators: Show spinners, progress bars, or status messages to inform users when operations are in progress
  • Use progress indicators: Provide feedback during encryption and decryption operations
  • Consider state management: Design your application to handle pending states gracefully
Example UI pattern: Use .onStep(callback) to track encryption progress. The callback fires at the start and end of each step, receiving the current EncryptStep enum value and a context object with isStart, isEnd, and duration (milliseconds, only meaningful on isEnd).
import { createCofheClient, createCofheConfig } from '@cofhe/sdk/web';
import { Encryptable, EncryptStep } from '@cofhe/sdk';
import { chains } from '@cofhe/sdk/chains';

const [isEncrypting, setIsEncrypting] = useState(false);
const [currentStep, setCurrentStep] = useState<string | null>(null);

const handleEncrypt = async () => {
  setIsEncrypting(true);
  try {
    const result = await cofheClient
      .encryptInputs([Encryptable.uint32(5n)])
      .onStep((step, ctx) => {
        if (ctx?.isStart) setCurrentStep(step);
        if (ctx?.isEnd) console.log(`${step} done in ${ctx.duration}ms`);
      })
      .execute();
    // Handle result
  } finally {
    setIsEncrypting(false);
    setCurrentStep(null);
  }
};
The EncryptStep enum values fire in order: InitTfheFetchKeysPackProveVerify.
CoFHE operations may take time to complete, especially on testnets. Always provide user feedback during these operations to improve user experience.

Development Workflow

Start with Mock Environment

Begin development using mock contracts for faster iteration:
  • Faster feedback loop: Mock environment provides immediate results without network delays
  • Easier debugging: Plaintext values are visible in mock contracts, making debugging simpler
  • No external dependencies: Test locally without connecting to testnets

Test Thoroughly

Write comprehensive tests covering:
  • Both environments: Test in mock and testnet environments
  • Edge cases: Handle zero values, maximum values, and boundary conditions
  • Error scenarios: Test what happens when operations fail or permissions are missing

Gas Optimization

Be aware of gas costs:
  • Mock environments simulate higher costs: Gas costs in mock environments are higher than production
  • Test on testnet for accurate estimates: Always test gas consumption on testnet before deployment
  • Optimize before deployment: Review and optimize your contract’s gas usage

Summary

Following these best practices will help you:
  • Build secure contracts: Proper permission management and avoiding information leakage
  • Optimize performance: Minimize operations and reuse encrypted constants
  • Improve user experience: Handle asynchronous operations gracefully
  • Develop efficiently: Use mock environments for rapid iteration
By following these best practices, you’ll create more secure, efficient, and maintainable FHE-enabled smart contracts.

Next Steps