Bitwise Operations
🟡 Intermediate | 📦 Basics
Encrypted bit manipulation: AND, OR, XOR, and bit shifting
Overview
This example demonstrates bitwise operations on encrypted integers in FHEVM. Learn how to use FHE.and() for masking and extracting bits, FHE.or() for setting bits, FHE.xor() for toggling bits, FHE.shl() for left shifting (multiply by 2^n), and FHE.shr() for right shifting (divide by 2^n). These operations are essential for permission flags, bitfields, and efficient power-of-2 arithmetic.
Quick Start
# Create new project from this template
npx labz create bitwise my-project
# Navigate and install
cd my-project
npm install
# Run tests
npx hardhat testContract
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import { FHE, euint8, euint64, externalEuint8, externalEuint64 } from "@fhevm/solidity/lib/FHE.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
/// @title Bitwise Operations - Encrypted bit manipulation
/// @notice Demonstrates FHE.and(), FHE.or(), FHE.xor(), FHE.shl(), FHE.shr()
/// @dev Bitwise operations on encrypted integers for flags, masks, and bit manipulation
contract BitwiseOps is ZamaEthereumConfig {
/// @dev Store operation results
euint8 private _result8;
euint64 private _result64;
// ============ Events ============
event AndDone(address indexed user);
event OrDone(address indexed user);
event XorDone(address indexed user);
event ShiftDone(address indexed user);
// ============ Bitwise AND ============
/// @notice Bitwise AND of two encrypted values
/// @dev Sets bits to 1 only where BOTH inputs have 1
/// @param a First encrypted value
/// @param b Second encrypted value
/// @param inputProof Zero-knowledge proof for the inputs
function bitwiseAnd(
externalEuint8 a,
externalEuint8 b,
bytes calldata inputProof
) external returns (euint8) {
euint8 encA = FHE.fromExternal(a, inputProof);
euint8 encB = FHE.fromExternal(b, inputProof);
// Bitwise AND: 1010 & 1100 = 1000
_result8 = FHE.and(encA, encB);
FHE.allowThis(_result8);
FHE.allow(_result8, msg.sender);
emit AndDone(msg.sender);
return _result8;
}
/// @notice Apply bitmask to extract specific bits
/// @dev Useful for reading flags from a bitfield
/// @param value Encrypted value with multiple flags
/// @param mask Plaintext mask (visible on-chain!)
/// @param inputProof Zero-knowledge proof for the input
function applyMask(
externalEuint8 value,
uint8 mask,
bytes calldata inputProof
) external returns (euint8) {
euint8 encValue = FHE.fromExternal(value, inputProof);
// AND with mask to extract specific bits
// e.g., value=0b11010110, mask=0b00001111 -> 0b00000110
_result8 = FHE.and(encValue, FHE.asEuint8(mask));
FHE.allowThis(_result8);
FHE.allow(_result8, msg.sender);
emit AndDone(msg.sender);
return _result8;
}
// ============ Bitwise OR ============
/// @notice Bitwise OR of two encrypted values
/// @dev Sets bits to 1 where EITHER input has 1
/// @param a First encrypted value
/// @param b Second encrypted value
/// @param inputProof Zero-knowledge proof for the inputs
function bitwiseOr(
externalEuint8 a,
externalEuint8 b,
bytes calldata inputProof
) external returns (euint8) {
euint8 encA = FHE.fromExternal(a, inputProof);
euint8 encB = FHE.fromExternal(b, inputProof);
// Bitwise OR: 1010 | 1100 = 1110
_result8 = FHE.or(encA, encB);
FHE.allowThis(_result8);
FHE.allow(_result8, msg.sender);
emit OrDone(msg.sender);
return _result8;
}
/// @notice Set specific bits using OR with mask
/// @dev Useful for setting flags in a bitfield
/// @param value Encrypted value
/// @param bitsToSet Plaintext mask of bits to set (visible!)
/// @param inputProof Zero-knowledge proof for the input
function setBits(
externalEuint8 value,
uint8 bitsToSet,
bytes calldata inputProof
) external returns (euint8) {
euint8 encValue = FHE.fromExternal(value, inputProof);
// OR with mask to set specific bits
// e.g., value=0b00000001, bitsToSet=0b00000010 -> 0b00000011
_result8 = FHE.or(encValue, FHE.asEuint8(bitsToSet));
FHE.allowThis(_result8);
FHE.allow(_result8, msg.sender);
emit OrDone(msg.sender);
return _result8;
}
// ============ Bitwise XOR ============
/// @notice Bitwise XOR of two encrypted values
/// @dev Sets bits to 1 where inputs DIFFER
/// @param a First encrypted value
/// @param b Second encrypted value
/// @param inputProof Zero-knowledge proof for the inputs
function bitwiseXor(
externalEuint8 a,
externalEuint8 b,
bytes calldata inputProof
) external returns (euint8) {
euint8 encA = FHE.fromExternal(a, inputProof);
euint8 encB = FHE.fromExternal(b, inputProof);
// Bitwise XOR: 1010 ^ 1100 = 0110
_result8 = FHE.xor(encA, encB);
FHE.allowThis(_result8);
FHE.allow(_result8, msg.sender);
emit XorDone(msg.sender);
return _result8;
}
/// @notice Toggle specific bits using XOR
/// @dev XOR with 1 flips the bit, XOR with 0 keeps it
/// @param value Encrypted value
/// @param bitsToToggle Plaintext mask of bits to toggle (visible!)
/// @param inputProof Zero-knowledge proof for the input
function toggleBits(
externalEuint8 value,
uint8 bitsToToggle,
bytes calldata inputProof
) external returns (euint8) {
euint8 encValue = FHE.fromExternal(value, inputProof);
// XOR to toggle specific bits
// e.g., value=0b00000011, toggle=0b00000001 -> 0b00000010
_result8 = FHE.xor(encValue, FHE.asEuint8(bitsToToggle));
FHE.allowThis(_result8);
FHE.allow(_result8, msg.sender);
emit XorDone(msg.sender);
return _result8;
}
// ============ Bit Shifting ============
/// @notice Shift left (multiply by powers of 2)
/// @dev Each shift left doubles the value
/// @param value Encrypted value to shift
/// @param positions Number of positions to shift (plaintext, visible!)
/// @param inputProof Zero-knowledge proof for the input
function shiftLeft(
externalEuint64 value,
uint8 positions,
bytes calldata inputProof
) external returns (euint64) {
euint64 encValue = FHE.fromExternal(value, inputProof);
// Shift left: 0b00000001 << 3 = 0b00001000 (1 -> 8)
// Equivalent to multiplying by 2^positions
_result64 = FHE.shl(encValue, FHE.asEuint8(positions));
FHE.allowThis(_result64);
FHE.allow(_result64, msg.sender);
emit ShiftDone(msg.sender);
return _result64;
}
/// @notice Shift right (divide by powers of 2)
/// @dev Each shift right halves the value (floors)
/// @param value Encrypted value to shift
/// @param positions Number of positions to shift (plaintext, visible!)
/// @param inputProof Zero-knowledge proof for the input
function shiftRight(
externalEuint64 value,
uint8 positions,
bytes calldata inputProof
) external returns (euint64) {
euint64 encValue = FHE.fromExternal(value, inputProof);
// Shift right: 0b00001000 >> 2 = 0b00000010 (8 -> 2)
// Equivalent to dividing by 2^positions (floored)
_result64 = FHE.shr(encValue, FHE.asEuint8(positions));
FHE.allowThis(_result64);
FHE.allow(_result64, msg.sender);
emit ShiftDone(msg.sender);
return _result64;
}
/// @notice Efficient multiply by power of 2
/// @dev Faster than regular multiplication for 2^n
/// @param value Encrypted value
/// @param power The power of 2 (e.g., 3 means multiply by 8)
/// @param inputProof Zero-knowledge proof for the input
function multiplyByPowerOf2(
externalEuint64 value,
uint8 power,
bytes calldata inputProof
) external returns (euint64) {
euint64 encValue = FHE.fromExternal(value, inputProof);
// value * 2^power using shift
_result64 = FHE.shl(encValue, FHE.asEuint8(power));
FHE.allowThis(_result64);
FHE.allow(_result64, msg.sender);
emit ShiftDone(msg.sender);
return _result64;
}
/// @notice Efficient divide by power of 2
/// @dev Faster than regular division for 2^n
/// @param value Encrypted value
/// @param power The power of 2 (e.g., 3 means divide by 8)
/// @param inputProof Zero-knowledge proof for the input
function divideByPowerOf2(
externalEuint64 value,
uint8 power,
bytes calldata inputProof
) external returns (euint64) {
euint64 encValue = FHE.fromExternal(value, inputProof);
// value / 2^power using shift (floored)
_result64 = FHE.shr(encValue, FHE.asEuint8(power));
FHE.allowThis(_result64);
FHE.allow(_result64, msg.sender);
emit ShiftDone(msg.sender);
return _result64;
}
// ============ View Functions ============
/// @notice Get last 8-bit result
function getResult8() external view returns (euint8) {
return _result8;
}
/// @notice Get last 64-bit result
function getResult64() external view returns (euint64) {
return _result64;
}
}
Code Explanation
Bitwise And
FHE.and() performs bitwise AND on encrypted integers. Sets bits to 1 only where BOTH inputs have 1. Essential for extracting specific bits using masks.
Lines 24-41
Apply Mask
Apply a bitmask to extract specific bits. Useful for reading individual flags from a bitfield. The mask is plaintext (visible) but the value stays encrypted.
Lines 45-59
Bitwise Or
FHE.or() performs bitwise OR on encrypted integers. Sets bits to 1 where EITHER input has 1. Perfect for combining or setting flags.
Lines 65-82
Bitwise Xor
FHE.xor() performs bitwise XOR on encrypted integers. Sets bits to 1 where inputs DIFFER. XOR with 1 flips the bit, XOR with 0 keeps it. Useful for toggling flags.
Lines 106-123
Shift Left
FHE.shl() shifts bits to the left. Equivalent to multiplying by 2^n. Faster than FHE.mul() for power-of-2 multiplication.
Lines 145-161
Shift Right
FHE.shr() shifts bits to the right. Equivalent to integer division by 2^n (floored). Faster than FHE.div() for power-of-2 division.
Lines 167-183
FHE Operations Used
FHE.and()FHE.or()FHE.xor()FHE.shl()FHE.shr()FHE.asEuint8()FHE.asEuint64()FHE.fromExternal()FHE.allowThis()FHE.allow()
FHE Types Used
euint8euint64externalEuint8externalEuint64
Tags
and or xor shl shr bitwise FHE.and FHE.or FHE.xor FHE.shl FHE.shr shift mask flags
Related Examples
Prerequisites
Before this example, you should understand:
Next Steps
After this example, check out:
Generated with Lab-Z
Last updated
