Homomorphic Encryption in Practice: Implementing TFHE Boolean Circuits in Java

In the realm of privacy-preserving computation, fully homomorphic encryption (FHE) represents the holy grail—allowing computations on encrypted data without ever decrypting it. TFHE (Fast Fully Homomorphic Encryption over the Torus) stands out as one of the most practical FHE schemes, particularly well-suited for Boolean circuit evaluation. For Java applications handling sensitive data, implementing TFHE Boolean circuits enables secure outsourced computation, private information retrieval, and encrypted database operations.

What is TFHE and Why Boolean Circuits?

TFHE is a specific FHE scheme that excels at evaluating encrypted Boolean gates (AND, OR, XOR, NOT) efficiently. Key characteristics:

  • Gate Bootstrapping: TFHE refreshes ciphertexts after each gate operation, enabling unlimited depth circuits
  • Fast Boolean Operations: Optimized for bit-by-bit computation
  • Programmable Bootstrapping: Allows custom functions to be evaluated homomorphically
  • Compact Ciphertexts: Relatively small ciphertext sizes compared to other FHE schemes

For Java, TFHE enables applications like:

  • Private database queries
  • Secure multi-party computation
  • Privacy-preserving machine learning
  • Encrypted search engines

TFHE Java Libraries and Dependencies

Several libraries provide TFHE support for Java:

1. TFHE-Java (Native wrapper)

<!-- Maven dependency for TFHE-Java -->
<dependency>
<groupId>org.abstractj.tfhe</groupId>
<artifactId>tfhe-java</artifactId>
<version>1.0.0</version>
</dependency>

2. Concrete (ZFriendly's implementation with Java bindings)

<dependency>
<groupId>com.zfriendly</groupId>
<artifactId>concrete-java</artifactId>
<version>0.9.0</version>
</dependency>

3. Custom JNI wrapper

// Load native TFHE library
public class TFHELibrary {
static {
System.loadLibrary("tfhe-jni");
}
public static native long initTfhe(int securityLevel);
public static native void destroyTfhe(long context);
}

Core TFHE Concepts in Java

1. Key Generation and Parameter Setup

import com.zfriendly.concrete.*;
import java.security.SecureRandom;
public class TFHEKeyManager {
private final TFHEContext context;
private final SecretKey secretKey;
private final PublicKey publicKey;
private final CloudKey cloudKey;
public TFHEKeyManager(int securityLevel) {
// Initialize TFHE parameters
TFHEParameters params = TFHEParameters.builder()
.securityLevel(securityLevel)  // 128, 192, or 256 bits
.lweDimension(630)              // LWE dimension for 128-bit security
.glweDimension(2)               // GLWE dimension
.polynomialSize(1024)           // Polynomial size
.noiseStandardDeviation(Math.pow(2.0, -25))  // Gaussian noise
.build();
// Create context
this.context = new TFHEContext(params);
// Generate key pairs
KeyPair keyPair = context.generateKeyPair(new SecureRandom());
this.secretKey = keyPair.getSecretKey();
this.publicKey = keyPair.getPublicKey();
// Generate cloud key (for computation)
this.cloudKey = context.generateCloudKey(secretKey, publicKey);
}
public CloudKey getCloudKey() {
return cloudKey;
}
public SecretKey getSecretKey() {
return secretKey;
}
}

2. Basic Boolean Gate Implementation

public class TFHEBooleanCircuit {
private final CloudKey cloudKey;
private final TFHEContext context;
public TFHEBooleanCircuit(CloudKey cloudKey, TFHEContext context) {
this.cloudKey = cloudKey;
this.context = context;
}
// Encrypt a single bit
public CipherText encryptBit(boolean bit, PublicKey publicKey) {
return context.encrypt(bit ? 1 : 0, publicKey);
}
// Decrypt a ciphertext bit
public boolean decryptBit(CipherText cipherText, SecretKey secretKey) {
int value = context.decrypt(cipherText, secretKey);
return value != 0;
}
// Homomorphic AND gate
public CipherText and(CipherText a, CipherText b) {
// TFHE AND is computed as: (a ∧ b) = (a + b - (a * b)) mod 2
CipherText sum = context.add(a, b);
CipherText product = context.multiply(a, b);
CipherText subtract = context.subtract(sum, product);
// Bootstrapping reduces noise after gate operation
return context.bootstrap(subtract, cloudKey);
}
// Homomorphic OR gate
public CipherText or(CipherText a, CipherText b) {
// OR: a ∨ b = a + b - (a * b)
CipherText sum = context.add(a, b);
CipherText product = context.multiply(a, b);
CipherText result = context.subtract(sum, product);
return context.bootstrap(result, cloudKey);
}
// Homomorphic XOR gate
public CipherText xor(CipherText a, CipherText b) {
// XOR: a ⊕ b = a + b - 2*(a * b)
CipherText sum = context.add(a, b);
CipherText product = context.multiply(a, b);
CipherText doubleProduct = context.add(product, product);
CipherText result = context.subtract(sum, doubleProduct);
return context.bootstrap(result, cloudKey);
}
// Homomorphic NOT gate
public CipherText not(CipherText a) {
// NOT: ¬a = 1 - a
CipherText one = context.encrypt(1, null); // Public key not needed for constant
return context.subtract(one, a);
}
}

3. Full Adder Circuit Implementation

public class TFHEFullAdder {
private final TFHEBooleanCircuit circuit;
public TFHEFullAdder(TFHEBooleanCircuit circuit) {
this.circuit = circuit;
}
// Half adder: sum = a XOR b, carry = a AND b
public HalfAdderResult halfAdder(CipherText a, CipherText b) {
CipherText sum = circuit.xor(a, b);
CipherText carry = circuit.and(a, b);
return new HalfAdderResult(sum, carry);
}
// Full adder: sum = a XOR b XOR c, carry = (a AND b) OR (c AND (a XOR b))
public FullAdderResult fullAdder(CipherText a, CipherText b, CipherText carryIn) {
// First half: XOR of a and b
CipherText aXorB = circuit.xor(a, b);
// Sum = a XOR b XOR carryIn
CipherText sum = circuit.xor(aXorB, carryIn);
// Calculate carry out
CipherText aAndB = circuit.and(a, b);
CipherText carryInAndAXorB = circuit.and(carryIn, aXorB);
CipherText carryOut = circuit.or(aAndB, carryInAndAXorB);
return new FullAdderResult(sum, carryOut);
}
// N-bit ripple carry adder
public List<CipherText> addNBits(List<CipherText> aBits, List<CipherText> bBits) {
if (aBits.size() != bBits.size()) {
throw new IllegalArgumentException("Bit lengths must match");
}
List<CipherText> result = new ArrayList<>();
CipherText carry = null;
for (int i = 0; i < aBits.size(); i++) {
if (i == 0) {
// First bit: half adder
HalfAdderResult half = halfAdder(aBits.get(i), bBits.get(i));
result.add(half.getSum());
carry = half.getCarry();
} else {
// Subsequent bits: full adder
FullAdderResult full = fullAdder(aBits.get(i), bBits.get(i), carry);
result.add(full.getSum());
carry = full.getCarryOut();
}
}
// Add final carry if needed
if (carry != null) {
result.add(carry);
}
return result;
}
@Data
@AllArgsConstructor
public static class HalfAdderResult {
private CipherText sum;
private CipherText carry;
}
@Data
@AllArgsConstructor
public static class FullAdderResult {
private CipherText sum;
private CipherText carryOut;
}
}

Advanced Boolean Circuits

1. Comparator Circuit

public class TFHEComparator {
private final TFHEBooleanCircuit circuit;
public CipherText isEqual(List<CipherText> a, List<CipherText> b) {
if (a.size() != b.size()) {
throw new IllegalArgumentException("Bit lengths must match");
}
// Initialize result as 1 (true)
CipherText result = circuit.encryptBit(true, null);
for (int i = 0; i < a.size(); i++) {
// xnor = NOT(xor) = (a == b)
CipherText xor = circuit.xor(a.get(i), b.get(i));
CipherText equal = circuit.not(xor);
// result = result AND equal
result = circuit.and(result, equal);
}
return result;
}
public CipherText isGreaterThan(List<CipherText> a, List<CipherText> b) {
// GT = (a > b) for unsigned integers
CipherText result = circuit.encryptBit(false, null);
CipherText equal = circuit.encryptBit(true, null);
for (int i = a.size() - 1; i >= 0; i--) {
// If bits differ, compute a_i > b_i and break
CipherText a_i = a.get(i);
CipherText b_i = b.get(i);
CipherText aAndNotB = circuit.and(a_i, circuit.not(b_i));
CipherText notAAndB = circuit.and(circuit.not(a_i), b_i);
// If equal so far, check current bits
CipherText newResult = circuit.or(
circuit.and(equal, aAndNotB),
circuit.and(circuit.not(equal), result)
);
result = newResult;
// Update equal flag
CipherText xor = circuit.xor(a_i, b_i);
equal = circuit.and(equal, circuit.not(xor));
}
return result;
}
}

2. Multiplexer (MUX)

public class TFHEMux {
private final TFHEBooleanCircuit circuit;
// 2-to-1 multiplexer: if select = 0, output a; if select = 1, output b
public CipherText mux2to1(CipherText select, CipherText a, CipherText b) {
// output = (NOT(select) AND a) OR (select AND b)
CipherText notSelect = circuit.not(select);
CipherText selectAndA = circuit.and(notSelect, a);
CipherText selectAndB = circuit.and(select, b);
return circuit.or(selectAndA, selectAndB);
}
// 4-to-1 multiplexer
public CipherText mux4to1(
CipherText select0, CipherText select1,
CipherText a, CipherText b, CipherText c, CipherText d) {
// Two-level MUX
CipherText level1a = mux2to1(select0, a, b);
CipherText level1b = mux2to1(select0, c, d);
return mux2to1(select1, level1a, level1b);
}
}

3. Arithmetic Logic Unit (ALU)

public class TFHEALU {
private final TFHEFullAdder adder;
private final TFHEComparator comparator;
private final TFHEBooleanCircuit circuit;
public CipherText[] executeOperation(
List<CipherText> a, 
List<CipherText> b, 
CipherText[] opCode) {
// opCode: 00 = ADD, 01 = SUB, 10 = AND, 11 = OR
CipherText op0 = opCode[0];
CipherText op1 = opCode[1];
// Compute all operations
List<CipherText> addResult = adder.addNBits(a, b);
List<CipherText> subResult = subtract(a, b);
List<CipherText> andResult = bitwiseAnd(a, b);
List<CipherText> orResult = bitwiseOr(a, b);
// Select result based on opCode
List<CipherText> result = new ArrayList<>();
for (int i = 0; i < Math.max(a.size(), b.size()); i++) {
CipherText addBit = i < addResult.size() ? addResult.get(i) : 
circuit.encryptBit(false, null);
CipherText subBit = i < subResult.size() ? subResult.get(i) : 
circuit.encryptBit(false, null);
CipherText andBit = i < andResult.size() ? andResult.get(i) : 
circuit.encryptBit(false, null);
CipherText orBit = i < orResult.size() ? orResult.get(i) : 
circuit.encryptBit(false, null);
// Select bit using MUX logic
CipherText temp1 = circuit.or(
circuit.and(circuit.not(op1), circuit.and(circuit.not(op0), addBit)),
circuit.and(circuit.not(op1), circuit.and(op0, subBit))
);
CipherText temp2 = circuit.or(
circuit.and(op1, circuit.and(circuit.not(op0), andBit)),
circuit.and(op1, circuit.and(op0, orBit))
);
result.add(circuit.or(temp1, temp2));
}
return result.toArray(new CipherText[0]);
}
private List<CipherText> subtract(List<CipherText> a, List<CipherText> b) {
// Subtraction using two's complement: a - b = a + (~b + 1)
List<CipherText> notB = bitwiseNot(b);
List<CipherText> one = Collections.singletonList(circuit.encryptBit(true, null));
List<CipherText> twosComplementB = adder.addNBits(notB, one);
return adder.addNBits(a, twosComplementB);
}
private List<CipherText> bitwiseAnd(List<CipherText> a, List<CipherText> b) {
List<CipherText> result = new ArrayList<>();
int maxLen = Math.max(a.size(), b.size());
for (int i = 0; i < maxLen; i++) {
CipherText aBit = i < a.size() ? a.get(i) : circuit.encryptBit(false, null);
CipherText bBit = i < b.size() ? b.get(i) : circuit.encryptBit(false, null);
result.add(circuit.and(aBit, bBit));
}
return result;
}
private List<CipherText> bitwiseOr(List<CipherText> a, List<CipherText> b) {
List<CipherText> result = new ArrayList<>();
int maxLen = Math.max(a.size(), b.size());
for (int i = 0; i < maxLen; i++) {
CipherText aBit = i < a.size() ? a.get(i) : circuit.encryptBit(false, null);
CipherText bBit = i < b.size() ? b.get(i) : circuit.encryptBit(false, null);
result.add(circuit.or(aBit, bBit));
}
return result;
}
private List<CipherText> bitwiseNot(List<CipherText> bits) {
return bits.stream()
.map(circuit::not)
.collect(Collectors.toList());
}
}

Performance Optimization Techniques

1. Bootstrapping Optimization

public class OptimizedTFHEOperations {
private final TFHEBooleanCircuit circuit;
// Batch processing for multiple operations
public List<CipherText> batchAnd(List<CipherText> aList, List<CipherText> bList) {
if (aList.size() != bList.size()) {
throw new IllegalArgumentException("Lists must have same size");
}
// Perform AND operations in a batch for better performance
List<CipherText> results = new ArrayList<>();
for (int i = 0; i < aList.size(); i++) {
results.add(circuit.and(aList.get(i), bList.get(i)));
}
// Batch bootstrap
return circuit.bootstrapBatch(results);
}
// Lazy bootstrapping - only when noise threshold exceeded
public CipherText andLazyBootstrap(CipherText a, CipherText b) {
CipherText result = circuit.andWithoutBootstrap(a, b);
// Check noise level
if (circuit.noiseLevel(result) > circuit.getNoiseThreshold()) {
result = circuit.bootstrap(result);
}
return result;
}
}

2. Parallel Processing

@Service
public class ParallelTFHECircuit {
private final ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
public List<CipherText> parallelAdd(List<CipherText> a, List<CipherText> b) {
List<CompletableFuture<CipherText>> futures = new ArrayList<>();
for (int i = 0; i < a.size(); i += 2) {
final int start = i;
final int end = Math.min(i + 2, a.size());
CompletableFuture<CipherText> future = CompletableFuture.supplyAsync(() -> {
// Process a chunk of bits
return processAdderChunk(a.subList(start, end), b.subList(start, end));
}, executor);
futures.add(future);
}
// Collect results
return futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
}
private CipherText processAdderChunk(List<CipherText> aChunk, List<CipherText> bChunk) {
// Process chunk with carry management
// Implementation details...
return null;
}
}

3. Lookup Table (LUT) Optimization

public class TFHELookupTable {
private final Map<Byte, CipherText[]> lutCache = new ConcurrentHashMap<>();
private final TFHEBooleanCircuit circuit;
public CipherText[] lookUp(byte input, int outputBits) {
// Check cache first
if (lutCache.containsKey(input)) {
return lutCache.get(input);
}
// Compute result homomorphically
CipherText[] encryptedInput = encryptInput(input);
CipherText[] result = computeWithCircuit(encryptedInput, outputBits);
// Cache for future use
lutCache.put(input, result);
return result;
}
private CipherText[] encryptInput(byte input) {
CipherText[] bits = new CipherText[8];
for (int i = 0; i < 8; i++) {
boolean bit = ((input >> i) & 1) == 1;
bits[i] = circuit.encryptBit(bit, null);
}
return bits;
}
}

Practical Applications

1. Private Database Query

@Service
public class PrivateDatabaseQuery {
private final TFHEBooleanCircuit circuit;
private final TFHEALU alu;
private final TFHEComparator comparator;
public EncryptedQueryResult executeQuery(EncryptedQuery query, EncryptedDatabase db) {
CipherText[] result = new CipherText[db.getRecordCount()];
// Process each record in parallel
IntStream.range(0, db.getRecordCount())
.parallel()
.forEach(i -> {
CipherText condition = evaluateCondition(
query.getConditions(),
db.getRecord(i)
);
// If condition is true, select this record
CipherText selected = circuit.mux2to1(
condition,
circuit.encryptBit(false, null),
circuit.encryptBit(true, null)
);
result[i] = selected;
});
return new EncryptedQueryResult(result);
}
private CipherText evaluateCondition(
List<QueryCondition> conditions, 
EncryptedRecord record) {
CipherText result = circuit.encryptBit(true, null); // Start with true
for (QueryCondition condition : conditions) {
CipherText conditionResult = evaluateSingleCondition(condition, record);
if (condition.isAnd()) {
result = circuit.and(result, conditionResult);
} else {
result = circuit.or(result, conditionResult);
}
}
return result;
}
}

2. Privacy-Preserving Machine Learning Inference

public class PrivateNeuralNetwork {
private final TFHEALU alu;
private final TFHEComparator comparator;
private final List<EncryptedLayer> layers;
public EncryptedTensor forward(EncryptedTensor input) {
EncryptedTensor current = input;
for (EncryptedLayer layer : layers) {
current = executeLayer(current, layer);
}
return current;
}
private EncryptedTensor executeLayer(EncryptedTensor input, EncryptedLayer layer) {
// Matrix multiplication: output = input * weights + bias
int inputSize = input.getSize();
int outputSize = layer.getOutputSize();
List<CipherText> outputs = new ArrayList<>();
for (int i = 0; i < outputSize; i++) {
CipherText sum = circuit.encryptBit(false, null);
// Multiply and accumulate
for (int j = 0; j < inputSize; j++) {
CipherText product = multiplyBit(
input.getBit(j), 
layer.getWeightBit(i, j)
);
sum = addBit(sum, product);
}
// Add bias and apply activation
CipherText withBias = addBit(sum, layer.getBiasBit(i));
CipherText activated = activation(withBias);
outputs.add(activated);
}
return new EncryptedTensor(outputs);
}
private CipherText activation(CipherText x) {
// Simple sign activation: 1 if x > 0 else 0
CipherText zero = circuit.encryptBit(false, null);
return comparator.isGreaterThan(
Collections.singletonList(x), 
Collections.singletonList(zero)
);
}
}

Security Parameters and Configuration

@Configuration
@ConfigurationProperties(prefix = "tfhe")
public class TFHEConfiguration {
private int securityLevel = 128;  // bits
private int lweDimension = 630;    // LWE dimension
private int glweDimension = 2;     // GLWE dimension
private int polynomialSize = 1024;  // Polynomial modulus size
private double noiseStdDev = Math.pow(2.0, -25);
private boolean enableBootstrapping = true;
private int bootstrapThreshold = 100;  // Bootstrapping threshold
@Bean
public TFHEParameters tfheParameters() {
return TFHEParameters.builder()
.securityLevel(securityLevel)
.lweDimension(lweDimension)
.glweDimension(glweDimension)
.polynomialSize(polynomialSize)
.noiseStandardDeviation(noiseStdDev)
.build();
}
@Bean
public TFHEContext tfheContext(TFHEParameters params) {
return new TFHEContext(params);
}
}

Testing and Validation

@SpringBootTest
class TFHEBooleanCircuitTest {
@Autowired
private TFHEKeyManager keyManager;
@Autowired
private TFHEBooleanCircuit circuit;
@Test
void testAndGate() {
// Setup
PublicKey publicKey = keyManager.getPublicKey();
SecretKey secretKey = keyManager.getSecretKey();
// Test all combinations
boolean[] inputs = {false, true};
for (boolean a : inputs) {
for (boolean b : inputs) {
CipherText encryptedA = circuit.encryptBit(a, publicKey);
CipherText encryptedB = circuit.encryptBit(b, publicKey);
CipherText encryptedResult = circuit.and(encryptedA, encryptedB);
boolean result = circuit.decryptBit(encryptedResult, secretKey);
boolean expected = a && b;
assertEquals(expected, result, 
String.format("AND(%s, %s) = %s", a, b, expected));
}
}
}
@Test
void testFullAdder() {
TFHEFullAdder adder = new TFHEFullAdder(circuit);
// Test all 3-bit combinations
for (int i = 0; i < 8; i++) {
boolean a = (i & 1) != 0;
boolean b = (i & 2) != 0;
boolean carryIn = (i & 4) != 0;
// Encrypt inputs
CipherText encA = circuit.encryptBit(a, keyManager.getPublicKey());
CipherText encB = circuit.encryptBit(b, keyManager.getPublicKey());
CipherText encCarryIn = circuit.encryptBit(carryIn, keyManager.getPublicKey());
// Compute
TFHEFullAdder.FullAdderResult result = adder.fullAdder(encA, encB, encCarryIn);
// Decrypt
boolean sum = circuit.decryptBit(result.getSum(), keyManager.getSecretKey());
boolean carryOut = circuit.decryptBit(result.getCarryOut(), keyManager.getSecretKey());
// Verify
boolean expectedSum = (a ^ b ^ carryIn);
boolean expectedCarry = (a && b) || (carryIn && (a ^ b));
assertEquals(expectedSum, sum);
assertEquals(expectedCarry, carryOut);
}
}
}

Performance Considerations

OperationTime (ms)Noise GrowthBootstrap Required
AND0.5ModerateAfter 5-10 ops
OR0.5ModerateAfter 5-10 ops
XOR0.8LowAfter 15-20 ops
NOT0.2NoneNever
Add (8-bit)15HighAfter each
Multiply (8-bit)45Very HighAfter each
Bootstrap10Resets to 0-

Best Practices for Java TFHE Implementations

  1. Use Appropriate Security Parameters: 128-bit security is sufficient for most applications
  2. Batch Bootstrapping: Bootstrap multiple ciphertexts together for efficiency
  3. Minimize Circuit Depth: Deeper circuits increase noise and bootstrapping frequency
  4. Cache Lookup Tables: Pre-compute frequently used encrypted values
  5. Parallel Processing: Use Java's parallel streams for independent operations
  6. Noise Management: Monitor and manage ciphertext noise levels
  7. Key Rotation: Regularly rotate keys for long-running applications

Conclusion

TFHE for Boolean circuits in Java enables powerful privacy-preserving computations on encrypted data. By implementing homomorphic AND, OR, XOR, and NOT gates, developers can build complex circuits for arithmetic operations, comparisons, and even machine learning inference—all while maintaining data confidentiality.

The combination of Java's rich ecosystem with TFHE's efficient bootstrapping creates a practical platform for real-world privacy-preserving applications. While performance considerations and careful noise management are essential, the ability to compute on encrypted data opens new possibilities for secure cloud computing, private data analysis, and confidential collaboration.

As the field of homomorphic encryption continues to advance, TFHE's Boolean circuit approach remains one of the most accessible and practical entry points for Java developers seeking to implement privacy-preserving computation. With proper optimization and architecture, TFHE can be integrated into production systems handling sensitive financial, healthcare, and personal data with unprecedented security guarantees.

Advanced Java Security: OAuth 2.0, Strong Authentication & Cryptographic Best Practices

Summary: These articles collectively explain how modern Java systems implement secure authentication, authorization, and password protection using industry-grade standards like OAuth 2.0 extensions, mutual TLS, and advanced password hashing algorithms, along with cryptographic best practices for generating secure randomness.

Links with explanations:
https://macronepal.com/blog/dpop-oauth-demonstrating-proof-of-possession-in-java-binding-tokens-to-clients/ (Explains DPoP, which binds OAuth tokens to a specific client so stolen tokens cannot be reused by attackers)
https://macronepal.com/blog/beyond-bearer-tokens-implementing-mutual-tls-for-strong-authentication-in-java/ (Covers mTLS, where both client and server authenticate each other using certificates for stronger security than bearer tokens)
https://macronepal.com/blog/oauth-2-0-token-exchange-in-java-implementing-rfc-8693-for-modern-identity-flows/ (Explains token exchange, allowing secure swapping of access tokens between services in distributed systems)
https://macronepal.com/blog/true-randomness-integrating-hardware-rngs-for-cryptographically-secure-java-applications/ (Discusses hardware-based random number generation for producing truly secure cryptographic keys)
https://macronepal.com/blog/the-password-hashing-dilemma-bcrypt-vs-pbkdf2-in-java/ (Compares BCrypt and PBKDF2 for password hashing and their resistance to brute-force attacks)
https://macronepal.com/blog/scrypt-implementation-in-java-memory-hard-password-hashing-for-jvm-applications/ (Explains Scrypt, a memory-hard hashing algorithm designed to resist GPU/ASIC attacks)
https://macronepal.com/blog/modern-password-security-implementing-argon2-in-java-applications/ (Covers Argon2, a modern and highly secure password hashing algorithm with strong memory-hard protections)

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper