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

Decrypt Carefully

When working with encrypted data, always consider what information you’re exposing through decryption operations. Key principles:
  • Evaluate information leakage: Before decrypting data, evaluate what information you’re exposing, how your code branches based on decrypted values, and what an observer might learn
  • Minimize decryption operations: Only decrypt when absolutely necessary and after all sensitive computations are complete
  • Consider timing attacks: Be aware that decryption timing can leak information about your data
Decrypting data exposes it to the blockchain and any observers. Always consider whether decryption is necessary 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 decrypt to make branching decisions
if (FHE.decrypt(condition)) {
    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:
// Show loading state during encryption
const [isEncrypting, setIsEncrypting] = useState(false);

const handleEncrypt = async () => {
  setIsEncrypting(true);
  try {
    const result = await cofhejs.encrypt([Encryptable.uint32(5n)], (step) => {
      console.log(`Encryption step: ${step}`);
      // Update UI with progress
    });
    // Handle result
  } finally {
    setIsEncrypting(false);
  }
};
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