mirror of
https://github.com/luxfi/fhe.git
synced 2026-07-26 23:16:08 +00:00
Restore core FHE Go files that were removed during cleanup. These are required for precompile/fhe to build.
1076 lines
30 KiB
C++
1076 lines
30 KiB
C++
// SPDX-License-Identifier: BSD-3-Clause
|
|
// Copyright (c) 2024-2025, Lux Industries Inc
|
|
//
|
|
// Optimized Encrypted Comparison Implementation
|
|
//
|
|
// Kogge-Stone parallel prefix algorithm for FHE comparison
|
|
// - O(log n) circuit depth vs O(n) for serial comparison
|
|
// - GPU acceleration via Metal/CUDA kernels
|
|
// - Early termination hints for common cases
|
|
//
|
|
// Patent: PAT-FHE-C8 - Encrypted Comparison for Solidity
|
|
// For enterprise licensing: fhe@lux.network
|
|
|
|
#include "encrypted_compare.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
namespace luxfhe {
|
|
namespace compare {
|
|
|
|
// =============================================================================
|
|
// Forward Declarations
|
|
// =============================================================================
|
|
|
|
class EncryptedBit;
|
|
class EncryptedInteger;
|
|
class FHEEngine;
|
|
|
|
// =============================================================================
|
|
// EncryptedBit - Single encrypted boolean
|
|
// =============================================================================
|
|
|
|
class EncryptedBit {
|
|
public:
|
|
EncryptedBit() = default;
|
|
explicit EncryptedBit(LuxFHECiphertext handle) : handle_(handle) {}
|
|
|
|
LuxFHECiphertext handle() const { return handle_; }
|
|
bool isValid() const { return handle_ != nullptr; }
|
|
|
|
// Homomorphic operations
|
|
static std::shared_ptr<EncryptedBit> AND(
|
|
const EncryptedBit& a, const EncryptedBit& b, FHEEngine* engine);
|
|
static std::shared_ptr<EncryptedBit> OR(
|
|
const EncryptedBit& a, const EncryptedBit& b, FHEEngine* engine);
|
|
static std::shared_ptr<EncryptedBit> XOR(
|
|
const EncryptedBit& a, const EncryptedBit& b, FHEEngine* engine);
|
|
static std::shared_ptr<EncryptedBit> NOT(
|
|
const EncryptedBit& a, FHEEngine* engine);
|
|
|
|
private:
|
|
LuxFHECiphertext handle_ = nullptr;
|
|
};
|
|
|
|
// =============================================================================
|
|
// EncryptedInteger - Vector of encrypted bits (LSB at index 0)
|
|
// =============================================================================
|
|
|
|
class EncryptedInteger {
|
|
public:
|
|
EncryptedInteger() = default;
|
|
explicit EncryptedInteger(std::vector<std::shared_ptr<EncryptedBit>> bits)
|
|
: bits_(std::move(bits)) {}
|
|
|
|
size_t numBits() const { return bits_.size(); }
|
|
const std::shared_ptr<EncryptedBit>& bit(size_t i) const { return bits_[i]; }
|
|
std::shared_ptr<EncryptedBit>& bit(size_t i) { return bits_[i]; }
|
|
|
|
LuxFHEInteger handle() const { return handle_; }
|
|
void setHandle(LuxFHEInteger h) { handle_ = h; }
|
|
|
|
private:
|
|
std::vector<std::shared_ptr<EncryptedBit>> bits_;
|
|
LuxFHEInteger handle_ = nullptr;
|
|
};
|
|
|
|
// =============================================================================
|
|
// FHE Engine Wrapper
|
|
// =============================================================================
|
|
|
|
class FHEEngine {
|
|
public:
|
|
explicit FHEEngine(LuxFHEEngine handle) : handle_(handle) {}
|
|
|
|
LuxFHEEngine handle() const { return handle_; }
|
|
|
|
// Gate operations (these call into the C API)
|
|
LuxFHECiphertext gate_and(LuxFHECiphertext a, LuxFHECiphertext b);
|
|
LuxFHECiphertext gate_or(LuxFHECiphertext a, LuxFHECiphertext b);
|
|
LuxFHECiphertext gate_xor(LuxFHECiphertext a, LuxFHECiphertext b);
|
|
LuxFHECiphertext gate_not(LuxFHECiphertext a);
|
|
LuxFHECiphertext gate_mux(LuxFHECiphertext sel, LuxFHECiphertext a, LuxFHECiphertext b);
|
|
|
|
private:
|
|
LuxFHEEngine handle_;
|
|
};
|
|
|
|
// =============================================================================
|
|
// ParallelCompare Implementation
|
|
// =============================================================================
|
|
|
|
class ParallelCompare::Impl {
|
|
public:
|
|
Config config_;
|
|
std::unique_ptr<FHEEngine> engine_;
|
|
std::unique_ptr<GPUCompareKernel> gpu_kernel_;
|
|
|
|
// Statistics
|
|
LuxFHECompareStats stats_ = {};
|
|
|
|
Impl(Config config) : config_(std::move(config)) {
|
|
if (config_.use_gpu) {
|
|
gpu_kernel_ = createGPUCompareKernel();
|
|
}
|
|
}
|
|
|
|
// Number of Kogge-Stone stages for n bits
|
|
uint32_t numStages() const {
|
|
return static_cast<uint32_t>(std::ceil(std::log2(config_.num_bits)));
|
|
}
|
|
};
|
|
|
|
ParallelCompare::ParallelCompare(Config config)
|
|
: impl_(std::make_unique<Impl>(std::move(config))) {}
|
|
|
|
ParallelCompare::~ParallelCompare() = default;
|
|
|
|
ParallelCompare::ParallelCompare(ParallelCompare&&) noexcept = default;
|
|
ParallelCompare& ParallelCompare::operator=(ParallelCompare&&) noexcept = default;
|
|
|
|
// =============================================================================
|
|
// Kogge-Stone Parallel Prefix for Less-Than
|
|
// =============================================================================
|
|
|
|
/*
|
|
* Kogge-Stone Parallel Prefix Comparison Algorithm
|
|
*
|
|
* For comparing a < b with n-bit integers:
|
|
*
|
|
* 1. Compute initial GP pairs for each bit position i:
|
|
* G[i] = (NOT a[i]) AND b[i] // a[i] < b[i] at this position
|
|
* P[i] = a[i] XNOR b[i] // a[i] == b[i] (propagate)
|
|
*
|
|
* 2. Kogge-Stone parallel prefix:
|
|
* For stage s in 0..log2(n)-1:
|
|
* For each i >= 2^s:
|
|
* (G[i], P[i]) = (G[i], P[i]) o (G[i-2^s], P[i-2^s])
|
|
*
|
|
* Where (G1, P1) o (G0, P0) = (G1 OR (P1 AND G0), P1 AND P0)
|
|
*
|
|
* 3. Result: G[n-1] is true iff a < b
|
|
*
|
|
* Circuit depth: O(log n) instead of O(n) for ripple comparison
|
|
*/
|
|
|
|
std::vector<GPPair> ParallelCompare::buildGPPairs(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
assert(a.numBits() == b.numBits());
|
|
const size_t n = a.numBits();
|
|
|
|
std::vector<GPPair> gp_pairs(n);
|
|
|
|
// Build initial GP pairs in parallel
|
|
// G[i] = (NOT a[i]) AND b[i] -- a[i] < b[i]
|
|
// P[i] = NOT(a[i] XOR b[i]) -- a[i] == b[i]
|
|
for (size_t i = 0; i < n; ++i) {
|
|
// NOT a[i]
|
|
auto not_a = EncryptedBit::NOT(*a.bit(i), impl_->engine_.get());
|
|
|
|
// G[i] = (NOT a[i]) AND b[i]
|
|
auto G = EncryptedBit::AND(*not_a, *b.bit(i), impl_->engine_.get());
|
|
|
|
// a[i] XOR b[i]
|
|
auto xor_ab = EncryptedBit::XOR(*a.bit(i), *b.bit(i), impl_->engine_.get());
|
|
|
|
// P[i] = NOT(a[i] XOR b[i]) = a[i] XNOR b[i]
|
|
auto P = EncryptedBit::NOT(*xor_ab, impl_->engine_.get());
|
|
|
|
gp_pairs[i] = {std::move(G), std::move(P)};
|
|
}
|
|
|
|
return gp_pairs;
|
|
}
|
|
|
|
GPPair ParallelCompare::parallelPrefix(const std::vector<GPPair>& initial_gp) {
|
|
if (initial_gp.empty()) {
|
|
throw std::invalid_argument("Empty GP pairs");
|
|
}
|
|
|
|
const size_t n = initial_gp.size();
|
|
const uint32_t num_stages = impl_->numStages();
|
|
|
|
// Working copy of GP pairs
|
|
std::vector<GPPair> gp_pairs = initial_gp;
|
|
|
|
// Kogge-Stone parallel prefix
|
|
for (uint32_t stage = 0; stage < num_stages; ++stage) {
|
|
const uint32_t stride = 1u << stage;
|
|
|
|
if (impl_->config_.use_gpu && impl_->gpu_kernel_) {
|
|
// GPU-accelerated stage computation
|
|
dispatchGPUKernel(gp_pairs, stage, stride);
|
|
} else {
|
|
// CPU fallback: process each position that has a valid pair to combine
|
|
std::vector<GPPair> new_gp_pairs(n);
|
|
|
|
for (size_t i = 0; i < n; ++i) {
|
|
if (i >= stride) {
|
|
// Combine (G[i], P[i]) with (G[i-stride], P[i-stride])
|
|
// Result: (G[i] OR (P[i] AND G[i-stride]), P[i] AND P[i-stride])
|
|
|
|
// P[i] AND G[i-stride]
|
|
auto p_and_g = EncryptedBit::AND(
|
|
*gp_pairs[i].P,
|
|
*gp_pairs[i - stride].G,
|
|
impl_->engine_.get()
|
|
);
|
|
|
|
// G[i] OR (P[i] AND G[i-stride])
|
|
auto new_G = EncryptedBit::OR(
|
|
*gp_pairs[i].G,
|
|
*p_and_g,
|
|
impl_->engine_.get()
|
|
);
|
|
|
|
// P[i] AND P[i-stride]
|
|
auto new_P = EncryptedBit::AND(
|
|
*gp_pairs[i].P,
|
|
*gp_pairs[i - stride].P,
|
|
impl_->engine_.get()
|
|
);
|
|
|
|
new_gp_pairs[i] = {std::move(new_G), std::move(new_P)};
|
|
} else {
|
|
// No pair to combine with, keep as-is
|
|
new_gp_pairs[i] = gp_pairs[i];
|
|
}
|
|
}
|
|
|
|
gp_pairs = std::move(new_gp_pairs);
|
|
}
|
|
|
|
impl_->stats_.gpu_kernel_launches++;
|
|
}
|
|
|
|
// Return the final GP pair at MSB position
|
|
return gp_pairs[n - 1];
|
|
}
|
|
|
|
void ParallelCompare::dispatchGPUKernel(
|
|
const std::vector<GPPair>& gp_pairs,
|
|
uint32_t stage,
|
|
uint32_t stride
|
|
) {
|
|
if (!impl_->gpu_kernel_) {
|
|
throw std::runtime_error("GPU kernel not available");
|
|
}
|
|
|
|
GPUCompareKernel::LaunchParams params;
|
|
params.workgroup_size = 256;
|
|
params.num_workgroups = (gp_pairs.size() + params.workgroup_size - 1) / params.workgroup_size;
|
|
|
|
// Note: const_cast because GPU kernel may modify in-place
|
|
// In practice, would use double-buffering
|
|
impl_->gpu_kernel_->launchKoggeStoneStage(
|
|
params,
|
|
const_cast<std::vector<GPPair>&>(gp_pairs),
|
|
stage
|
|
);
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::lessThan(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
if (a.numBits() != b.numBits()) {
|
|
throw std::invalid_argument("Bit width mismatch");
|
|
}
|
|
|
|
impl_->stats_.total_comparisons++;
|
|
|
|
// Build initial GP pairs
|
|
auto gp_pairs = buildGPPairs(a, b);
|
|
|
|
// Parallel prefix to get final result
|
|
auto final_gp = parallelPrefix(gp_pairs);
|
|
|
|
// G[MSB] indicates a < b
|
|
return final_gp.G;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Equality Check (Parallel XOR-Reduction)
|
|
// =============================================================================
|
|
|
|
/*
|
|
* Parallel Equality Check
|
|
*
|
|
* a == b iff all bits are equal: AND of (a[i] XNOR b[i]) for all i
|
|
*
|
|
* Using parallel reduction:
|
|
* 1. Compute XOR for each bit: d[i] = a[i] XOR b[i]
|
|
* 2. OR-reduce all d[i]: any_diff = d[0] OR d[1] OR ... OR d[n-1]
|
|
* 3. Result: NOT any_diff
|
|
*
|
|
* Depth: O(log n) using tree reduction
|
|
*/
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::equal(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
if (a.numBits() != b.numBits()) {
|
|
throw std::invalid_argument("Bit width mismatch");
|
|
}
|
|
|
|
impl_->stats_.total_comparisons++;
|
|
|
|
const size_t n = a.numBits();
|
|
|
|
// Step 1: Compute XOR for each bit position
|
|
std::vector<std::shared_ptr<EncryptedBit>> xor_bits(n);
|
|
for (size_t i = 0; i < n; ++i) {
|
|
xor_bits[i] = EncryptedBit::XOR(*a.bit(i), *b.bit(i), impl_->engine_.get());
|
|
}
|
|
|
|
// Step 2: OR-reduce using tree
|
|
if (impl_->config_.use_gpu && impl_->gpu_kernel_) {
|
|
GPUCompareKernel::LaunchParams params;
|
|
params.workgroup_size = 256;
|
|
impl_->gpu_kernel_->launchXorReduction(params, xor_bits);
|
|
impl_->stats_.gpu_kernel_launches++;
|
|
} else {
|
|
// CPU tree reduction
|
|
while (xor_bits.size() > 1) {
|
|
std::vector<std::shared_ptr<EncryptedBit>> reduced;
|
|
reduced.reserve((xor_bits.size() + 1) / 2);
|
|
|
|
for (size_t i = 0; i + 1 < xor_bits.size(); i += 2) {
|
|
auto ored = EncryptedBit::OR(*xor_bits[i], *xor_bits[i + 1], impl_->engine_.get());
|
|
reduced.push_back(std::move(ored));
|
|
}
|
|
|
|
// Handle odd element
|
|
if (xor_bits.size() % 2 == 1) {
|
|
reduced.push_back(xor_bits.back());
|
|
}
|
|
|
|
xor_bits = std::move(reduced);
|
|
}
|
|
}
|
|
|
|
// Step 3: NOT to get equality result
|
|
return EncryptedBit::NOT(*xor_bits[0], impl_->engine_.get());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Derived Comparison Operations
|
|
// =============================================================================
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::lessEqual(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
// a <= b iff NOT (a > b) iff NOT (b < a)
|
|
auto b_lt_a = lessThan(b, a);
|
|
return EncryptedBit::NOT(*b_lt_a, impl_->engine_.get());
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::greaterThan(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
// a > b iff b < a
|
|
return lessThan(b, a);
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::greaterEqual(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
// a >= b iff NOT (a < b)
|
|
auto a_lt_b = lessThan(a, b);
|
|
return EncryptedBit::NOT(*a_lt_b, impl_->engine_.get());
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::notEqual(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
// a != b iff NOT (a == b)
|
|
auto eq = equal(a, b);
|
|
return EncryptedBit::NOT(*eq, impl_->engine_.get());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Selection Operations (Oblivious)
|
|
// =============================================================================
|
|
|
|
std::shared_ptr<EncryptedInteger> ParallelCompare::select(
|
|
const EncryptedBit& cond,
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
if (a.numBits() != b.numBits()) {
|
|
throw std::invalid_argument("Bit width mismatch");
|
|
}
|
|
|
|
const size_t n = a.numBits();
|
|
std::vector<std::shared_ptr<EncryptedBit>> result_bits(n);
|
|
|
|
// MUX for each bit: cond ? a[i] : b[i]
|
|
// MUX(s, a, b) = (s AND a) OR ((NOT s) AND b)
|
|
// Using CMUX in FHE: more efficient single gate
|
|
|
|
if (impl_->config_.use_gpu && impl_->gpu_kernel_) {
|
|
auto result = std::make_shared<EncryptedInteger>();
|
|
GPUCompareKernel::LaunchParams params;
|
|
params.workgroup_size = 256;
|
|
impl_->gpu_kernel_->launchSelect(params, cond, a, b, *result);
|
|
return result;
|
|
}
|
|
|
|
// CPU fallback
|
|
for (size_t i = 0; i < n; ++i) {
|
|
// s AND a[i]
|
|
auto s_and_a = EncryptedBit::AND(cond, *a.bit(i), impl_->engine_.get());
|
|
|
|
// NOT s
|
|
auto not_s = EncryptedBit::NOT(cond, impl_->engine_.get());
|
|
|
|
// (NOT s) AND b[i]
|
|
auto not_s_and_b = EncryptedBit::AND(*not_s, *b.bit(i), impl_->engine_.get());
|
|
|
|
// (s AND a[i]) OR ((NOT s) AND b[i])
|
|
result_bits[i] = EncryptedBit::OR(*s_and_a, *not_s_and_b, impl_->engine_.get());
|
|
}
|
|
|
|
return std::make_shared<EncryptedInteger>(std::move(result_bits));
|
|
}
|
|
|
|
std::shared_ptr<EncryptedInteger> ParallelCompare::min(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
// min(a, b) = (a < b) ? a : b
|
|
auto a_lt_b = lessThan(a, b);
|
|
return select(*a_lt_b, a, b);
|
|
}
|
|
|
|
std::shared_ptr<EncryptedInteger> ParallelCompare::max(
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b
|
|
) {
|
|
// max(a, b) = (a > b) ? a : b = (b < a) ? a : b
|
|
auto b_lt_a = lessThan(b, a);
|
|
return select(*b_lt_a, a, b);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Batch Operations
|
|
// =============================================================================
|
|
|
|
std::vector<std::shared_ptr<EncryptedBit>> ParallelCompare::batchLessThan(
|
|
const std::vector<EncryptedInteger>& a_vec,
|
|
const std::vector<EncryptedInteger>& b_vec
|
|
) {
|
|
if (a_vec.size() != b_vec.size()) {
|
|
throw std::invalid_argument("Batch size mismatch");
|
|
}
|
|
|
|
std::vector<std::shared_ptr<EncryptedBit>> results;
|
|
results.reserve(a_vec.size());
|
|
|
|
// TODO: GPU kernel for batch processing
|
|
// For now, sequential
|
|
for (size_t i = 0; i < a_vec.size(); ++i) {
|
|
results.push_back(lessThan(a_vec[i], b_vec[i]));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<EncryptedBit>> ParallelCompare::batchEqual(
|
|
const std::vector<EncryptedInteger>& a_vec,
|
|
const std::vector<EncryptedInteger>& b_vec
|
|
) {
|
|
if (a_vec.size() != b_vec.size()) {
|
|
throw std::invalid_argument("Batch size mismatch");
|
|
}
|
|
|
|
std::vector<std::shared_ptr<EncryptedBit>> results;
|
|
results.reserve(a_vec.size());
|
|
|
|
for (size_t i = 0; i < a_vec.size(); ++i) {
|
|
results.push_back(equal(a_vec[i], b_vec[i]));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Scalar Comparisons (Optimized for plaintext operand)
|
|
// =============================================================================
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::lessThanScalar(
|
|
const EncryptedInteger& a,
|
|
const std::vector<uint8_t>& b_plaintext
|
|
) {
|
|
// Optimization: for plaintext b, we can simplify gates
|
|
// If b[i] = 0: G[i] = 0, P[i] = NOT a[i]
|
|
// If b[i] = 1: G[i] = NOT a[i], P[i] = a[i]
|
|
|
|
const size_t n = a.numBits();
|
|
std::vector<GPPair> gp_pairs(n);
|
|
|
|
for (size_t i = 0; i < n; ++i) {
|
|
// Extract bit from plaintext
|
|
size_t byte_idx = i / 8;
|
|
size_t bit_idx = i % 8;
|
|
bool b_bit = (byte_idx < b_plaintext.size()) ?
|
|
((b_plaintext[byte_idx] >> bit_idx) & 1) : false;
|
|
|
|
auto not_a = EncryptedBit::NOT(*a.bit(i), impl_->engine_.get());
|
|
|
|
if (b_bit) {
|
|
// b[i] = 1: G = NOT a[i], P = a[i]
|
|
gp_pairs[i].G = not_a;
|
|
gp_pairs[i].P = a.bit(i);
|
|
} else {
|
|
// b[i] = 0: G = 0 (encrypted), P = NOT a[i]
|
|
// G = 0 means this position never generates "less than"
|
|
// We can use encrypted zero or optimize away
|
|
gp_pairs[i].G = nullptr; // Represents encrypted 0
|
|
gp_pairs[i].P = not_a;
|
|
}
|
|
}
|
|
|
|
// Parallel prefix with optimization for null G values
|
|
auto final_gp = parallelPrefix(gp_pairs);
|
|
return final_gp.G;
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> ParallelCompare::equalScalar(
|
|
const EncryptedInteger& a,
|
|
const std::vector<uint8_t>& b_plaintext
|
|
) {
|
|
// For equality with plaintext, we need a[i] == b[i] for all i
|
|
// If b[i] = 0: need a[i] = 0, i.e., NOT a[i]
|
|
// If b[i] = 1: need a[i] = 1, i.e., a[i]
|
|
|
|
const size_t n = a.numBits();
|
|
std::vector<std::shared_ptr<EncryptedBit>> match_bits(n);
|
|
|
|
for (size_t i = 0; i < n; ++i) {
|
|
size_t byte_idx = i / 8;
|
|
size_t bit_idx = i % 8;
|
|
bool b_bit = (byte_idx < b_plaintext.size()) ?
|
|
((b_plaintext[byte_idx] >> bit_idx) & 1) : false;
|
|
|
|
if (b_bit) {
|
|
// Need a[i] = 1
|
|
match_bits[i] = a.bit(i);
|
|
} else {
|
|
// Need a[i] = 0
|
|
match_bits[i] = EncryptedBit::NOT(*a.bit(i), impl_->engine_.get());
|
|
}
|
|
}
|
|
|
|
// AND-reduce all match bits
|
|
while (match_bits.size() > 1) {
|
|
std::vector<std::shared_ptr<EncryptedBit>> reduced;
|
|
reduced.reserve((match_bits.size() + 1) / 2);
|
|
|
|
for (size_t i = 0; i + 1 < match_bits.size(); i += 2) {
|
|
auto anded = EncryptedBit::AND(*match_bits[i], *match_bits[i + 1], impl_->engine_.get());
|
|
reduced.push_back(std::move(anded));
|
|
}
|
|
|
|
if (match_bits.size() % 2 == 1) {
|
|
reduced.push_back(match_bits.back());
|
|
}
|
|
|
|
match_bits = std::move(reduced);
|
|
}
|
|
|
|
return match_bits[0];
|
|
}
|
|
|
|
// =============================================================================
|
|
// EncryptedBit Operations (Stubs - would call into FHE library)
|
|
// =============================================================================
|
|
|
|
std::shared_ptr<EncryptedBit> EncryptedBit::AND(
|
|
const EncryptedBit& a, const EncryptedBit& b, FHEEngine* engine
|
|
) {
|
|
if (!engine) throw std::runtime_error("Engine required");
|
|
auto result = engine->gate_and(a.handle(), b.handle());
|
|
return std::make_shared<EncryptedBit>(result);
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> EncryptedBit::OR(
|
|
const EncryptedBit& a, const EncryptedBit& b, FHEEngine* engine
|
|
) {
|
|
if (!engine) throw std::runtime_error("Engine required");
|
|
auto result = engine->gate_or(a.handle(), b.handle());
|
|
return std::make_shared<EncryptedBit>(result);
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> EncryptedBit::XOR(
|
|
const EncryptedBit& a, const EncryptedBit& b, FHEEngine* engine
|
|
) {
|
|
if (!engine) throw std::runtime_error("Engine required");
|
|
auto result = engine->gate_xor(a.handle(), b.handle());
|
|
return std::make_shared<EncryptedBit>(result);
|
|
}
|
|
|
|
std::shared_ptr<EncryptedBit> EncryptedBit::NOT(
|
|
const EncryptedBit& a, FHEEngine* engine
|
|
) {
|
|
if (!engine) throw std::runtime_error("Engine required");
|
|
auto result = engine->gate_not(a.handle());
|
|
return std::make_shared<EncryptedBit>(result);
|
|
}
|
|
|
|
// =============================================================================
|
|
// GPU Kernel Factory (Platform-Specific)
|
|
// =============================================================================
|
|
|
|
#ifdef LUXFHE_GPU_METAL
|
|
|
|
class MetalGPUCompareKernel : public GPUCompareKernel {
|
|
public:
|
|
void launchKoggeStoneStage(
|
|
const LaunchParams& params,
|
|
std::vector<GPPair>& gp_pairs,
|
|
uint32_t stage
|
|
) override {
|
|
// Metal shader dispatch for Kogge-Stone stage
|
|
// Each thread computes one GP pair update
|
|
// Uses threadgroup memory for shared access
|
|
|
|
/*
|
|
* Metal shader pseudocode:
|
|
*
|
|
* kernel void kogge_stone_stage(
|
|
* device GPPair* gp_pairs [[buffer(0)]],
|
|
* constant uint& stage [[buffer(1)]],
|
|
* constant uint& n [[buffer(2)]],
|
|
* uint tid [[thread_position_in_grid]]
|
|
* ) {
|
|
* uint stride = 1u << stage;
|
|
* if (tid >= stride && tid < n) {
|
|
* GPPair high = gp_pairs[tid];
|
|
* GPPair low = gp_pairs[tid - stride];
|
|
*
|
|
* // (G1, P1) o (G0, P0) = (G1 OR (P1 AND G0), P1 AND P0)
|
|
* // These are encrypted operations done via FHE gates
|
|
* gp_pairs[tid].G = fhe_or(high.G, fhe_and(high.P, low.G));
|
|
* gp_pairs[tid].P = fhe_and(high.P, low.P);
|
|
* }
|
|
* }
|
|
*/
|
|
|
|
// TODO: Implement Metal kernel dispatch
|
|
(void)params;
|
|
(void)gp_pairs;
|
|
(void)stage;
|
|
}
|
|
|
|
void launchXorReduction(
|
|
const LaunchParams& params,
|
|
std::vector<std::shared_ptr<EncryptedBit>>& xor_bits
|
|
) override {
|
|
// Metal parallel reduction kernel
|
|
(void)params;
|
|
(void)xor_bits;
|
|
}
|
|
|
|
void launchSelect(
|
|
const LaunchParams& params,
|
|
const EncryptedBit& cond,
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b,
|
|
EncryptedInteger& result
|
|
) override {
|
|
// Metal CMUX kernel for all bits in parallel
|
|
(void)params;
|
|
(void)cond;
|
|
(void)a;
|
|
(void)b;
|
|
(void)result;
|
|
}
|
|
};
|
|
|
|
#endif // LUXFHE_GPU_METAL
|
|
|
|
#ifdef LUXFHE_GPU_CUDA
|
|
|
|
class CUDAGPUCompareKernel : public GPUCompareKernel {
|
|
public:
|
|
void launchKoggeStoneStage(
|
|
const LaunchParams& params,
|
|
std::vector<GPPair>& gp_pairs,
|
|
uint32_t stage
|
|
) override {
|
|
/*
|
|
* CUDA kernel pseudocode:
|
|
*
|
|
* __global__ void kogge_stone_stage(
|
|
* GPPair* gp_pairs,
|
|
* uint32_t stage,
|
|
* uint32_t n
|
|
* ) {
|
|
* uint32_t tid = blockIdx.x * blockDim.x + threadIdx.x;
|
|
* uint32_t stride = 1u << stage;
|
|
*
|
|
* if (tid >= stride && tid < n) {
|
|
* GPPair high = gp_pairs[tid];
|
|
* GPPair low = gp_pairs[tid - stride];
|
|
*
|
|
* // FHE gate operations (batched for coalesced memory access)
|
|
* gp_pairs[tid].G = fhe_or(high.G, fhe_and(high.P, low.G));
|
|
* gp_pairs[tid].P = fhe_and(high.P, low.P);
|
|
* }
|
|
* }
|
|
*/
|
|
|
|
// TODO: Implement CUDA kernel dispatch
|
|
(void)params;
|
|
(void)gp_pairs;
|
|
(void)stage;
|
|
}
|
|
|
|
void launchXorReduction(
|
|
const LaunchParams& params,
|
|
std::vector<std::shared_ptr<EncryptedBit>>& xor_bits
|
|
) override {
|
|
(void)params;
|
|
(void)xor_bits;
|
|
}
|
|
|
|
void launchSelect(
|
|
const LaunchParams& params,
|
|
const EncryptedBit& cond,
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b,
|
|
EncryptedInteger& result
|
|
) override {
|
|
(void)params;
|
|
(void)cond;
|
|
(void)a;
|
|
(void)b;
|
|
(void)result;
|
|
}
|
|
};
|
|
|
|
#endif // LUXFHE_GPU_CUDA
|
|
|
|
// CPU fallback kernel (no GPU)
|
|
class CPUCompareKernel : public GPUCompareKernel {
|
|
public:
|
|
void launchKoggeStoneStage(
|
|
const LaunchParams& params,
|
|
std::vector<GPPair>& gp_pairs,
|
|
uint32_t stage
|
|
) override {
|
|
// CPU implementation is in ParallelCompare::parallelPrefix
|
|
(void)params;
|
|
(void)gp_pairs;
|
|
(void)stage;
|
|
}
|
|
|
|
void launchXorReduction(
|
|
const LaunchParams& params,
|
|
std::vector<std::shared_ptr<EncryptedBit>>& xor_bits
|
|
) override {
|
|
(void)params;
|
|
(void)xor_bits;
|
|
}
|
|
|
|
void launchSelect(
|
|
const LaunchParams& params,
|
|
const EncryptedBit& cond,
|
|
const EncryptedInteger& a,
|
|
const EncryptedInteger& b,
|
|
EncryptedInteger& result
|
|
) override {
|
|
(void)params;
|
|
(void)cond;
|
|
(void)a;
|
|
(void)b;
|
|
(void)result;
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<GPUCompareKernel> createGPUCompareKernel() {
|
|
#ifdef LUXFHE_GPU_METAL
|
|
return std::make_unique<MetalGPUCompareKernel>();
|
|
#elif defined(LUXFHE_GPU_CUDA)
|
|
return std::make_unique<CUDAGPUCompareKernel>();
|
|
#else
|
|
return std::make_unique<CPUCompareKernel>();
|
|
#endif
|
|
}
|
|
|
|
} // namespace compare
|
|
} // namespace luxfhe
|
|
|
|
// =============================================================================
|
|
// C API Implementation
|
|
// =============================================================================
|
|
|
|
extern "C" {
|
|
|
|
// Context management
|
|
struct LuxFHECompareContextImpl {
|
|
luxfhe::compare::ParallelCompare comparer;
|
|
LuxFHEEngine engine;
|
|
|
|
LuxFHECompareContextImpl(luxfhe::compare::ParallelCompare::Config cfg, LuxFHEEngine e)
|
|
: comparer(std::move(cfg)), engine(e) {}
|
|
};
|
|
|
|
LuxFHECompareContext luxfhe_compare_context_create(
|
|
LuxFHEEngine engine,
|
|
LuxFHEKoggeStoneConfig config
|
|
) {
|
|
luxfhe::compare::ParallelCompare::Config cfg;
|
|
cfg.num_bits = config.num_bits;
|
|
cfg.block_size = config.block_size;
|
|
cfg.use_gpu = config.use_gpu;
|
|
cfg.early_termination = config.early_termination;
|
|
cfg.batch_size = config.batch_size;
|
|
|
|
return new LuxFHECompareContextImpl(std::move(cfg), engine);
|
|
}
|
|
|
|
void luxfhe_compare_context_free(LuxFHECompareContext ctx) {
|
|
delete static_cast<LuxFHECompareContextImpl*>(ctx);
|
|
}
|
|
|
|
LuxFHEKoggeStoneStage luxfhe_compare_get_stage_info(
|
|
LuxFHECompareContext ctx,
|
|
uint32_t stage
|
|
) {
|
|
(void)ctx;
|
|
LuxFHEKoggeStoneStage info;
|
|
info.stage = stage;
|
|
info.stride = 1u << stage;
|
|
info.num_ops = 0; // TODO: compute based on num_bits
|
|
return info;
|
|
}
|
|
|
|
// Core operations - these wrap the C++ implementation
|
|
LuxFHECiphertext luxfhe_lt(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
// TODO: Implement full integration with FHE engine
|
|
return nullptr;
|
|
}
|
|
|
|
LuxFHECiphertext luxfhe_le(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
return nullptr;
|
|
}
|
|
|
|
LuxFHECiphertext luxfhe_gt(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
// gt(a, b) = lt(b, a)
|
|
return luxfhe_lt(engine, ctx, b, a);
|
|
}
|
|
|
|
LuxFHECiphertext luxfhe_ge(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
return nullptr;
|
|
}
|
|
|
|
LuxFHECiphertext luxfhe_eq(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
return nullptr;
|
|
}
|
|
|
|
LuxFHECiphertext luxfhe_ne(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
return nullptr;
|
|
}
|
|
|
|
LuxFHEInteger luxfhe_min(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
return nullptr;
|
|
}
|
|
|
|
LuxFHEInteger luxfhe_max(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
return nullptr;
|
|
}
|
|
|
|
// Batch operations
|
|
void* luxfhe_compare_batch(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHECompareBatch* batch
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)batch;
|
|
return nullptr;
|
|
}
|
|
|
|
void luxfhe_compare_batch_free(void* results, uint32_t count, LuxFHECompareOp op) {
|
|
(void)results;
|
|
(void)count;
|
|
(void)op;
|
|
}
|
|
|
|
// Scalar operations
|
|
LuxFHECiphertext luxfhe_lt_scalar(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
const uint8_t* b_bytes,
|
|
uint32_t b_len
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b_bytes;
|
|
(void)b_len;
|
|
return nullptr;
|
|
}
|
|
|
|
LuxFHECiphertext luxfhe_eq_scalar(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
const uint8_t* b_bytes,
|
|
uint32_t b_len
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b_bytes;
|
|
(void)b_len;
|
|
return nullptr;
|
|
}
|
|
|
|
// Statistics
|
|
LuxFHECompareStats luxfhe_compare_get_stats(LuxFHECompareContext ctx) {
|
|
(void)ctx;
|
|
LuxFHECompareStats stats = {};
|
|
return stats;
|
|
}
|
|
|
|
void luxfhe_compare_reset_stats(LuxFHECompareContext ctx) {
|
|
(void)ctx;
|
|
}
|
|
|
|
#ifdef LUXFHE_GPU_ENABLED
|
|
|
|
LuxFHEGPUKernelConfig luxfhe_compare_get_kernel_config(
|
|
LuxFHEEngine engine,
|
|
uint32_t num_bits,
|
|
uint32_t batch_size
|
|
) {
|
|
(void)engine;
|
|
(void)num_bits;
|
|
(void)batch_size;
|
|
LuxFHEGPUKernelConfig config = {};
|
|
config.workgroup_size = 256;
|
|
config.num_workgroups = (batch_size + 255) / 256;
|
|
return config;
|
|
}
|
|
|
|
int luxfhe_gpu_kogge_stone_prefix(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEGPUKernelConfig* config,
|
|
LuxFHEInteger* a_batch,
|
|
LuxFHEInteger* b_batch,
|
|
uint32_t batch_size,
|
|
LuxFHECiphertext* results
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)config;
|
|
(void)a_batch;
|
|
(void)b_batch;
|
|
(void)batch_size;
|
|
(void)results;
|
|
return 0;
|
|
}
|
|
|
|
LuxFHECiphertext luxfhe_gpu_early_term_hint(
|
|
LuxFHEEngine engine,
|
|
LuxFHECompareContext ctx,
|
|
LuxFHEInteger a,
|
|
LuxFHEInteger b
|
|
) {
|
|
(void)engine;
|
|
(void)ctx;
|
|
(void)a;
|
|
(void)b;
|
|
return nullptr;
|
|
}
|
|
|
|
#endif // LUXFHE_GPU_ENABLED
|
|
|
|
} // extern "C"
|