mirror of
https://github.com/luxfi/accel.git
synced 2026-10-30 17:03:45 +00:00
724 lines
18 KiB
Go
724 lines
18 KiB
Go
//go:build accel
|
|
|
|
package fhe
|
|
|
|
import (
|
|
"unsafe"
|
|
|
|
"github.com/luxfi/accel"
|
|
)
|
|
|
|
// GPU-accelerated FHE implementations.
|
|
// Uses kernels from fhe/ kernel collection.
|
|
// These dispatch to the accel library's GPU backends (CUDA, Metal, Vulkan)
|
|
// for parallel polynomial operations in RNS form.
|
|
|
|
func keyGenGPU(sess *accel.Session, params Params) (*SecretKey, *PublicKey, error) {
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
// Generate ternary secret key using GPU random sampling
|
|
// Kernel: Uses crypto/sha256 for CSPRNG seeding
|
|
skSize := n * numMods
|
|
pkSize := 2 * n * numMods
|
|
|
|
// Create output tensors
|
|
skTensor, err := accel.NewTensor[uint64](sess, []int{skSize})
|
|
if err != nil {
|
|
return keyGenCPU(params)
|
|
}
|
|
defer skTensor.Close()
|
|
|
|
pkTensor, err := accel.NewTensor[uint64](sess, []int{pkSize})
|
|
if err != nil {
|
|
return keyGenCPU(params)
|
|
}
|
|
defer pkTensor.Close()
|
|
|
|
// For key generation, we use lattice operations for polynomial sampling
|
|
// The NTT forward transform is used for key computation
|
|
// Fall back to CPU as keygen is typically done once
|
|
return keyGenCPU(params)
|
|
}
|
|
|
|
func genRelinKeyGPU(sess *accel.Session, params Params, sk *SecretKey) (*RelinKey, error) {
|
|
// Relinearization key generation requires:
|
|
// 1. Computing s^2 in NTT domain
|
|
// 2. Gadget decomposition
|
|
// 3. Encrypting decomposed components
|
|
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
// Create tensor for s^2 computation
|
|
skTensor, err := accel.NewTensorWithData[uint64](sess, []int{n * numMods}, sk.Data)
|
|
if err != nil {
|
|
return genRelinKeyCPU(params, sk)
|
|
}
|
|
defer skTensor.Close()
|
|
|
|
sk2Tensor, err := accel.NewTensor[uint64](sess, []int{n * numMods})
|
|
if err != nil {
|
|
return genRelinKeyCPU(params, sk)
|
|
}
|
|
defer sk2Tensor.Close()
|
|
|
|
// Compute s^2 using field multiplication
|
|
err = sess.ZK().FieldMul(
|
|
skTensor.Untyped(),
|
|
skTensor.Untyped(),
|
|
sk2Tensor.Untyped(),
|
|
params.CoeffMods[0],
|
|
)
|
|
if err != nil {
|
|
return genRelinKeyCPU(params, sk)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return genRelinKeyCPU(params, sk)
|
|
}
|
|
|
|
// Full relinkey generation is complex, use CPU
|
|
return genRelinKeyCPU(params, sk)
|
|
}
|
|
|
|
func genGaloisKeyGPU(sess *accel.Session, params Params, sk *SecretKey, steps []int) (*GaloisKey, error) {
|
|
// Galois key generation requires automorphism evaluation
|
|
// Fall back to CPU for initial implementation
|
|
return genGaloisKeyCPU(params, sk, steps)
|
|
}
|
|
|
|
func genBootstrapKeyGPU(sess *accel.Session, params Params, sk *SecretKey) (*BootstrapKey, error) {
|
|
// Bootstrap key is very large and complex
|
|
// Fall back to CPU for initial implementation
|
|
return genBootstrapKeyCPU(params, sk)
|
|
}
|
|
|
|
func encryptGPU(sess *accel.Session, params Params, pk *PublicKey, pt *Plaintext) (*Ciphertext, error) {
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
// Create tensors for plaintext and public key
|
|
ptTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(pt.Data)}, pt.Data)
|
|
if err != nil {
|
|
return encryptCPU(params, pk, pt)
|
|
}
|
|
defer ptTensor.Close()
|
|
|
|
pkTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(pk.Data)}, pk.Data)
|
|
if err != nil {
|
|
return encryptCPU(params, pk, pt)
|
|
}
|
|
defer pkTensor.Close()
|
|
|
|
// Output ciphertext tensor
|
|
ctSize := 2 * n * numMods
|
|
ctTensor, err := accel.NewTensor[uint64](sess, []int{ctSize})
|
|
if err != nil {
|
|
return encryptCPU(params, pk, pt)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
// Dispatch BFV/CKKS encryption kernel via FHE ops
|
|
fheOps := sess.FHE()
|
|
err = fheOps.BFVEncrypt(ptTensor.Untyped(), pkTensor.Untyped(), ctTensor.Untyped())
|
|
if err != nil {
|
|
return encryptCPU(params, pk, pt)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return encryptCPU(params, pk, pt)
|
|
}
|
|
|
|
// Read result
|
|
ctData, err := ctTensor.ToSlice()
|
|
if err != nil {
|
|
return encryptCPU(params, pk, pt)
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: ctData,
|
|
Scheme: params.Scheme,
|
|
Level: numMods - 1,
|
|
Scale: pt.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func decryptGPU(sess *accel.Session, params Params, sk *SecretKey, ct *Ciphertext) (*Plaintext, error) {
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
// Create tensors
|
|
ctTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct.Data)}, ct.Data)
|
|
if err != nil {
|
|
return decryptCPU(params, sk, ct)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
skTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(sk.Data)}, sk.Data)
|
|
if err != nil {
|
|
return decryptCPU(params, sk, ct)
|
|
}
|
|
defer skTensor.Close()
|
|
|
|
ptSize := n * numMods
|
|
ptTensor, err := accel.NewTensor[uint64](sess, []int{ptSize})
|
|
if err != nil {
|
|
return decryptCPU(params, sk, ct)
|
|
}
|
|
defer ptTensor.Close()
|
|
|
|
// Dispatch decryption kernel
|
|
err = sess.FHE().BFVDecrypt(ctTensor.Untyped(), skTensor.Untyped(), ptTensor.Untyped())
|
|
if err != nil {
|
|
return decryptCPU(params, sk, ct)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return decryptCPU(params, sk, ct)
|
|
}
|
|
|
|
ptData, err := ptTensor.ToSlice()
|
|
if err != nil {
|
|
return decryptCPU(params, sk, ct)
|
|
}
|
|
|
|
return &Plaintext{
|
|
Data: ptData,
|
|
Scheme: ct.Scheme,
|
|
Scale: ct.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func addGPU(sess *accel.Session, params Params, ct1, ct2 *Ciphertext) (*Ciphertext, error) {
|
|
// Ciphertext addition is component-wise modular addition
|
|
// This is highly parallelizable
|
|
|
|
// Create tensors
|
|
ct1Tensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct1.Data)}, ct1.Data)
|
|
if err != nil {
|
|
return addCPU(params, ct1, ct2)
|
|
}
|
|
defer ct1Tensor.Close()
|
|
|
|
ct2Tensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct2.Data)}, ct2.Data)
|
|
if err != nil {
|
|
return addCPU(params, ct1, ct2)
|
|
}
|
|
defer ct2Tensor.Close()
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{len(ct1.Data)})
|
|
if err != nil {
|
|
return addCPU(params, ct1, ct2)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Dispatch FHE add kernel
|
|
err = sess.FHE().BFVAdd(ct1Tensor.Untyped(), ct2Tensor.Untyped(), resultTensor.Untyped())
|
|
if err != nil {
|
|
return addCPU(params, ct1, ct2)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return addCPU(params, ct1, ct2)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return addCPU(params, ct1, ct2)
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct1.Scheme,
|
|
Level: minInt(ct1.Level, ct2.Level),
|
|
Scale: ct1.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func addPlainGPU(sess *accel.Session, params Params, ct *Ciphertext, pt *Plaintext) (*Ciphertext, error) {
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
// Create tensors
|
|
ctTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct.Data)}, ct.Data)
|
|
if err != nil {
|
|
return addPlainCPU(params, ct, pt)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
// Pad plaintext to match ciphertext size if needed
|
|
ptData := make([]uint64, n*numMods)
|
|
copy(ptData, pt.Data)
|
|
ptTensor, err := accel.NewTensorWithData[uint64](sess, []int{n * numMods}, ptData)
|
|
if err != nil {
|
|
return addPlainCPU(params, ct, pt)
|
|
}
|
|
defer ptTensor.Close()
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{len(ct.Data)})
|
|
if err != nil {
|
|
return addPlainCPU(params, ct, pt)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Use field add on first component (c0 + m)
|
|
err = sess.ZK().FieldAdd(
|
|
ctTensor.Untyped(),
|
|
ptTensor.Untyped(),
|
|
resultTensor.Untyped(),
|
|
params.CoeffMods[0],
|
|
)
|
|
if err != nil {
|
|
return addPlainCPU(params, ct, pt)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return addPlainCPU(params, ct, pt)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return addPlainCPU(params, ct, pt)
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct.Scheme,
|
|
Level: ct.Level,
|
|
Scale: ct.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func subGPU(sess *accel.Session, params Params, ct1, ct2 *Ciphertext) (*Ciphertext, error) {
|
|
// Subtraction via negation and addition
|
|
// c1 - c2 = c1 + (-c2)
|
|
|
|
ct1Tensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct1.Data)}, ct1.Data)
|
|
if err != nil {
|
|
return subCPU(params, ct1, ct2)
|
|
}
|
|
defer ct1Tensor.Close()
|
|
|
|
// Negate ct2: -x mod q = q - x
|
|
negCt2Data := make([]uint64, len(ct2.Data))
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
for i := range ct2.Data {
|
|
modIdx := (i / n) % numMods
|
|
mod := params.CoeffMods[modIdx]
|
|
if ct2.Data[i] == 0 {
|
|
negCt2Data[i] = 0
|
|
} else {
|
|
negCt2Data[i] = mod - ct2.Data[i]
|
|
}
|
|
}
|
|
|
|
negCt2Tensor, err := accel.NewTensorWithData[uint64](sess, []int{len(negCt2Data)}, negCt2Data)
|
|
if err != nil {
|
|
return subCPU(params, ct1, ct2)
|
|
}
|
|
defer negCt2Tensor.Close()
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{len(ct1.Data)})
|
|
if err != nil {
|
|
return subCPU(params, ct1, ct2)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Add ct1 + (-ct2)
|
|
err = sess.FHE().BFVAdd(ct1Tensor.Untyped(), negCt2Tensor.Untyped(), resultTensor.Untyped())
|
|
if err != nil {
|
|
return subCPU(params, ct1, ct2)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return subCPU(params, ct1, ct2)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return subCPU(params, ct1, ct2)
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct1.Scheme,
|
|
Level: minInt(ct1.Level, ct2.Level),
|
|
Scale: ct1.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func multiplyGPU(sess *accel.Session, params Params, ct1, ct2 *Ciphertext, rlk *RelinKey) (*Ciphertext, error) {
|
|
// FHE multiplication is the most computationally intensive operation
|
|
// It consists of:
|
|
// 1. Tensor product of polynomials (NTT-based)
|
|
// 2. Relinearization to reduce ciphertext size
|
|
|
|
ct1Tensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct1.Data)}, ct1.Data)
|
|
if err != nil {
|
|
return multiplyCPU(params, ct1, ct2, rlk)
|
|
}
|
|
defer ct1Tensor.Close()
|
|
|
|
ct2Tensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct2.Data)}, ct2.Data)
|
|
if err != nil {
|
|
return multiplyCPU(params, ct1, ct2, rlk)
|
|
}
|
|
defer ct2Tensor.Close()
|
|
|
|
rlkTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(rlk.Data)}, rlk.Data)
|
|
if err != nil {
|
|
return multiplyCPU(params, ct1, ct2, rlk)
|
|
}
|
|
defer rlkTensor.Close()
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{len(ct1.Data)})
|
|
if err != nil {
|
|
return multiplyCPU(params, ct1, ct2, rlk)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Dispatch FHE multiply kernel with relinearization
|
|
err = sess.FHE().BFVMultiply(
|
|
ct1Tensor.Untyped(),
|
|
ct2Tensor.Untyped(),
|
|
rlkTensor.Untyped(),
|
|
resultTensor.Untyped(),
|
|
)
|
|
if err != nil {
|
|
return multiplyCPU(params, ct1, ct2, rlk)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return multiplyCPU(params, ct1, ct2, rlk)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return multiplyCPU(params, ct1, ct2, rlk)
|
|
}
|
|
|
|
newScale := ct1.Scale
|
|
if ct1.Scheme == SchemeCKKS {
|
|
newScale = ct1.Scale * ct2.Scale
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct1.Scheme,
|
|
Level: minInt(ct1.Level, ct2.Level),
|
|
Scale: newScale,
|
|
}, nil
|
|
}
|
|
|
|
func multiplyPlainGPU(sess *accel.Session, params Params, ct *Ciphertext, pt *Plaintext) (*Ciphertext, error) {
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
ctTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct.Data)}, ct.Data)
|
|
if err != nil {
|
|
return multiplyPlainCPU(params, ct, pt)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
// Pad plaintext
|
|
ptData := make([]uint64, n*numMods)
|
|
copy(ptData, pt.Data)
|
|
ptTensor, err := accel.NewTensorWithData[uint64](sess, []int{n * numMods}, ptData)
|
|
if err != nil {
|
|
return multiplyPlainCPU(params, ct, pt)
|
|
}
|
|
defer ptTensor.Close()
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{len(ct.Data)})
|
|
if err != nil {
|
|
return multiplyPlainCPU(params, ct, pt)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Dispatch multiply plain kernel
|
|
err = sess.FHE().BFVMultiplyPlain(ctTensor.Untyped(), ptTensor.Untyped(), resultTensor.Untyped())
|
|
if err != nil {
|
|
return multiplyPlainCPU(params, ct, pt)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return multiplyPlainCPU(params, ct, pt)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return multiplyPlainCPU(params, ct, pt)
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct.Scheme,
|
|
Level: ct.Level,
|
|
Scale: ct.Scale * pt.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func rotateGPU(sess *accel.Session, params Params, ct *Ciphertext, gk *GaloisKey, steps int) (*Ciphertext, error) {
|
|
ctTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct.Data)}, ct.Data)
|
|
if err != nil {
|
|
return rotateCPU(params, ct, gk, steps)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
gkTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(gk.Data)}, gk.Data)
|
|
if err != nil {
|
|
return rotateCPU(params, ct, gk, steps)
|
|
}
|
|
defer gkTensor.Close()
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{len(ct.Data)})
|
|
if err != nil {
|
|
return rotateCPU(params, ct, gk, steps)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Dispatch rotation kernel
|
|
err = sess.FHE().BFVRotate(ctTensor.Untyped(), gkTensor.Untyped(), steps, resultTensor.Untyped())
|
|
if err != nil {
|
|
return rotateCPU(params, ct, gk, steps)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return rotateCPU(params, ct, gk, steps)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return rotateCPU(params, ct, gk, steps)
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct.Scheme,
|
|
Level: ct.Level,
|
|
Scale: ct.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func rescaleGPU(sess *accel.Session, params Params, ct *Ciphertext) (*Ciphertext, error) {
|
|
ctTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct.Data)}, ct.Data)
|
|
if err != nil {
|
|
return rescaleCPU(params, ct)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
// Result has one fewer RNS component
|
|
n := int(params.PolyDegree)
|
|
newNumMods := ct.Level
|
|
resultSize := 2 * n * newNumMods
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{resultSize})
|
|
if err != nil {
|
|
return rescaleCPU(params, ct)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Dispatch CKKS rescale kernel
|
|
err = sess.FHE().CKKSRescale(ctTensor.Untyped(), resultTensor.Untyped())
|
|
if err != nil {
|
|
return rescaleCPU(params, ct)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return rescaleCPU(params, ct)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return rescaleCPU(params, ct)
|
|
}
|
|
|
|
lastMod := params.CoeffMods[ct.Level]
|
|
newScale := ct.Scale / float64(lastMod)
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct.Scheme,
|
|
Level: ct.Level - 1,
|
|
Scale: newScale,
|
|
}, nil
|
|
}
|
|
|
|
func modSwitchGPU(sess *accel.Session, params Params, ct *Ciphertext) (*Ciphertext, error) {
|
|
// Modulus switching drops the last RNS component without rescaling
|
|
// This is simpler than rescale - just truncate
|
|
return modSwitchCPU(params, ct)
|
|
}
|
|
|
|
func bootstrapGPU(sess *accel.Session, params Params, ct *Ciphertext, bsk *BootstrapKey) (*Ciphertext, error) {
|
|
ctTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(ct.Data)}, ct.Data)
|
|
if err != nil {
|
|
return bootstrapCPU(params, ct, bsk)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
bskTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(bsk.Data)}, bsk.Data)
|
|
if err != nil {
|
|
return bootstrapCPU(params, ct, bsk)
|
|
}
|
|
defer bskTensor.Close()
|
|
|
|
// Result is at full level
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
resultSize := 2 * n * numMods
|
|
|
|
resultTensor, err := accel.NewTensor[uint64](sess, []int{resultSize})
|
|
if err != nil {
|
|
return bootstrapCPU(params, ct, bsk)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Dispatch bootstrap kernel
|
|
err = sess.FHE().Bootstrap(ctTensor.Untyped(), bskTensor.Untyped(), resultTensor.Untyped())
|
|
if err != nil {
|
|
return bootstrapCPU(params, ct, bsk)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return bootstrapCPU(params, ct, bsk)
|
|
}
|
|
|
|
resultData, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return bootstrapCPU(params, ct, bsk)
|
|
}
|
|
|
|
return &Ciphertext{
|
|
Data: resultData,
|
|
Scheme: ct.Scheme,
|
|
Level: numMods - 1,
|
|
Scale: params.Scale,
|
|
}, nil
|
|
}
|
|
|
|
func batchEncryptGPU(sess *accel.Session, params Params, pk *PublicKey, pts []*Plaintext) ([]*Ciphertext, error) {
|
|
m := len(pts)
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
// Pack all plaintexts into a single tensor
|
|
ptSize := n * numMods
|
|
allPtData := make([]uint64, m*ptSize)
|
|
for i, pt := range pts {
|
|
copy(allPtData[i*ptSize:(i+1)*ptSize], pt.Data)
|
|
}
|
|
|
|
ptTensor, err := accel.NewTensorWithData[uint64](sess, []int{m, ptSize}, allPtData)
|
|
if err != nil {
|
|
return batchEncryptCPU(params, pk, pts)
|
|
}
|
|
defer ptTensor.Close()
|
|
|
|
pkTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(pk.Data)}, pk.Data)
|
|
if err != nil {
|
|
return batchEncryptCPU(params, pk, pts)
|
|
}
|
|
defer pkTensor.Close()
|
|
|
|
ctSize := 2 * n * numMods
|
|
ctTensor, err := accel.NewTensor[uint64](sess, []int{m, ctSize})
|
|
if err != nil {
|
|
return batchEncryptCPU(params, pk, pts)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
// Dispatch batch encryption kernel
|
|
err = sess.FHE().BFVEncryptBatch(ptTensor.Untyped(), pkTensor.Untyped(), ctTensor.Untyped())
|
|
if err != nil {
|
|
return batchEncryptCPU(params, pk, pts)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return batchEncryptCPU(params, pk, pts)
|
|
}
|
|
|
|
allCtData, err := ctTensor.ToSlice()
|
|
if err != nil {
|
|
return batchEncryptCPU(params, pk, pts)
|
|
}
|
|
|
|
// Unpack results
|
|
results := make([]*Ciphertext, m)
|
|
for i := 0; i < m; i++ {
|
|
ctData := make([]uint64, ctSize)
|
|
copy(ctData, allCtData[i*ctSize:(i+1)*ctSize])
|
|
results[i] = &Ciphertext{
|
|
Data: ctData,
|
|
Scheme: params.Scheme,
|
|
Level: numMods - 1,
|
|
Scale: pts[i].Scale,
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func batchDecryptGPU(sess *accel.Session, params Params, sk *SecretKey, cts []*Ciphertext) ([]*Plaintext, error) {
|
|
m := len(cts)
|
|
n := int(params.PolyDegree)
|
|
numMods := len(params.CoeffMods)
|
|
|
|
// Pack all ciphertexts
|
|
ctSize := 2 * n * numMods
|
|
allCtData := make([]uint64, m*ctSize)
|
|
for i, ct := range cts {
|
|
copy(allCtData[i*ctSize:(i+1)*ctSize], ct.Data)
|
|
}
|
|
|
|
ctTensor, err := accel.NewTensorWithData[uint64](sess, []int{m, ctSize}, allCtData)
|
|
if err != nil {
|
|
return batchDecryptCPU(params, sk, cts)
|
|
}
|
|
defer ctTensor.Close()
|
|
|
|
skTensor, err := accel.NewTensorWithData[uint64](sess, []int{len(sk.Data)}, sk.Data)
|
|
if err != nil {
|
|
return batchDecryptCPU(params, sk, cts)
|
|
}
|
|
defer skTensor.Close()
|
|
|
|
ptSize := n * numMods
|
|
ptTensor, err := accel.NewTensor[uint64](sess, []int{m, ptSize})
|
|
if err != nil {
|
|
return batchDecryptCPU(params, sk, cts)
|
|
}
|
|
defer ptTensor.Close()
|
|
|
|
// Dispatch batch decryption - not directly supported, iterate
|
|
// This could be optimized with a proper batch kernel
|
|
results := make([]*Plaintext, m)
|
|
for i, ct := range cts {
|
|
pt, err := decryptGPU(sess, params, sk, ct)
|
|
if err != nil {
|
|
return batchDecryptCPU(params, sk, cts)
|
|
}
|
|
results[i] = pt
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func minInt(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func uint64SliceToBytes(data []uint64) []byte {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
return unsafe.Slice((*byte)(unsafe.Pointer(&data[0])), len(data)*8)
|
|
}
|