Files
2023-12-15 21:45:54 +01:00

429 lines
11 KiB
Go

package main
import (
"log"
"os"
"strconv"
"sync"
"time"
"github.com/luxdefi/lattice/v5/core/rlwe"
"github.com/luxdefi/lattice/v5/he/heint"
"github.com/luxdefi/lattice/v5/mhe"
"github.com/luxdefi/lattice/v5/ring"
"github.com/luxdefi/lattice/v5/utils/sampling"
)
func check(err error) {
if err != nil {
panic(err)
}
}
func runTimed(f func()) time.Duration {
start := time.Now()
f()
return time.Since(start)
}
func runTimedParty(f func(), N int) time.Duration {
start := time.Now()
f()
return time.Duration(time.Since(start).Nanoseconds() / int64(N))
}
type party struct {
sk *rlwe.SecretKey
rlkEphemSk *rlwe.SecretKey
ckgShare mhe.PublicKeyGenShare
rkgShareOne mhe.RelinearizationKeyGenShare
rkgShareTwo mhe.RelinearizationKeyGenShare
pcksShare mhe.PublicKeySwitchShare
input []uint64
}
type multTask struct {
wg *sync.WaitGroup
op1 *rlwe.Ciphertext
opOut *rlwe.Ciphertext
res *rlwe.Ciphertext
elapsedmultTask time.Duration
}
var elapsedEncryptParty time.Duration
var elapsedEncryptCloud time.Duration
var elapsedCKGCloud time.Duration
var elapsedCKGParty time.Duration
var elapsedRKGCloud time.Duration
var elapsedRKGParty time.Duration
var elapsedPCKSCloud time.Duration
var elapsedPCKSParty time.Duration
var elapsedEvalCloudCPU time.Duration
var elapsedEvalCloud time.Duration
var elapsedEvalParty time.Duration
func main() {
// For more details about the PSI example see
// Multiparty Homomorphic Encryption: From Theory to Practice (<https://eprint.iacr.org/2020/304>)
l := log.New(os.Stderr, "", 0)
// $go run main.go arg1 arg2
// arg1: number of parties
// arg2: number of Go routines
// Largest for n=8192: 512 parties
N := 8 // Default number of parties
var err error
if len(os.Args[1:]) >= 1 {
N, err = strconv.Atoi(os.Args[1])
check(err)
}
NGoRoutine := 1 // Default number of Go routines
if len(os.Args[1:]) >= 2 {
NGoRoutine, err = strconv.Atoi(os.Args[2])
check(err)
}
// Creating encryption parameters from a default params with logN=14, logQP=438 with a plaintext modulus T=65537
params, err := heint.NewParametersFromLiteral(heint.ParametersLiteral{
LogN: 14,
LogQ: []int{56, 55, 55, 54, 54, 54},
LogP: []int{55, 55},
PlaintextModulus: 65537,
})
if err != nil {
panic(err)
}
crs, err := sampling.NewKeyedPRNG([]byte{'l', 'a', 't', 't', 'i', 'g', 'o'})
if err != nil {
panic(err)
}
encoder := heint.NewEncoder(params)
// Target private and public keys
tsk, tpk := rlwe.NewKeyGenerator(params).GenKeyPairNew()
// Create each party, and allocate the memory for all the shares that the protocols will need
P := genparties(params, N)
// Inputs & expected result
expRes := genInputs(params, P)
// 1) Collective public key generation
pk := ckgphase(params, crs, P)
// 2) Collective relinearization key generation
rlk := rkgphase(params, crs, P)
evk := rlwe.NewMemEvaluationKeySet(rlk)
l.Printf("\tdone (cloud: %s, party: %s)\n",
elapsedRKGCloud, elapsedRKGParty)
l.Printf("\tSetup done (cloud: %s, party: %s)\n",
elapsedRKGCloud+elapsedCKGCloud, elapsedRKGParty+elapsedCKGParty)
encInputs := encPhase(params, P, pk, encoder)
encRes := evalPhase(params, NGoRoutine, encInputs, evk)
encOut := pcksPhase(params, tpk, encRes, P)
// Decrypt the result with the target secret key
l.Println("> ResulPlaintextModulus:")
decryptor := rlwe.NewDecryptor(params, tsk)
ptres := heint.NewPlaintext(params, params.MaxLevel())
elapsedDecParty := runTimed(func() {
decryptor.Decrypt(encOut, ptres)
})
// Check the result
res := make([]uint64, params.MaxSlots())
if err := encoder.Decode(ptres, res); err != nil {
panic(err)
}
l.Printf("\t%v\n", res[:16])
for i := range expRes {
if expRes[i] != res[i] {
//l.Printf("\t%v\n", expRes)
l.Println("\tincorrect")
return
}
}
l.Println("\tcorrect")
l.Printf("> Finished (total cloud: %s, total party: %s)\n",
elapsedCKGCloud+elapsedRKGCloud+elapsedEncryptCloud+elapsedEvalCloud+elapsedPCKSCloud,
elapsedCKGParty+elapsedRKGParty+elapsedEncryptParty+elapsedEvalParty+elapsedPCKSParty+elapsedDecParty)
}
func encPhase(params heint.Parameters, P []*party, pk *rlwe.PublicKey, encoder *heint.Encoder) (encInputs []*rlwe.Ciphertext) {
l := log.New(os.Stderr, "", 0)
encInputs = make([]*rlwe.Ciphertext, len(P))
for i := range encInputs {
encInputs[i] = heint.NewCiphertext(params, 1, params.MaxLevel())
}
// Each party encrypts its input vector
l.Println("> Encrypt Phase")
encryptor := rlwe.NewEncryptor(params, pk)
pt := heint.NewPlaintext(params, params.MaxLevel())
elapsedEncryptParty = runTimedParty(func() {
for i, pi := range P {
if err := encoder.Encode(pi.input, pt); err != nil {
panic(err)
}
if err := encryptor.Encrypt(pt, encInputs[i]); err != nil {
panic(err)
}
}
}, len(P))
elapsedEncryptCloud = time.Duration(0)
l.Printf("\tdone (cloud: %s, party: %s)\n", elapsedEncryptCloud, elapsedEncryptParty)
return
}
func evalPhase(params heint.Parameters, NGoRoutine int, encInputs []*rlwe.Ciphertext, evk rlwe.EvaluationKeySet) (encRes *rlwe.Ciphertext) {
l := log.New(os.Stderr, "", 0)
encLvls := make([][]*rlwe.Ciphertext, 0)
encLvls = append(encLvls, encInputs)
for nLvl := len(encInputs) / 2; nLvl > 0; nLvl = nLvl >> 1 {
encLvl := make([]*rlwe.Ciphertext, nLvl)
for i := range encLvl {
encLvl[i] = heint.NewCiphertext(params, 2, params.MaxLevel())
}
encLvls = append(encLvls, encLvl)
}
encRes = encLvls[len(encLvls)-1][0]
evaluator := heint.NewEvaluator(params, evk)
// Split the task among the Go routines
tasks := make(chan *multTask)
workers := &sync.WaitGroup{}
workers.Add(NGoRoutine)
//l.Println("> Spawning", NGoRoutine, "evaluator goroutine")
for i := 1; i <= NGoRoutine; i++ {
go func(i int) {
evaluator := evaluator.ShallowCopy() // creates a shallow evaluator copy for this goroutine
for task := range tasks {
task.elapsedmultTask = runTimed(func() {
// 1) Multiplication of two input vectors
if err := evaluator.Mul(task.op1, task.opOut, task.res); err != nil {
panic(err)
}
// 2) Relinearization
if err := evaluator.Relinearize(task.res, task.res); err != nil {
panic(err)
}
})
task.wg.Done()
}
//l.Println("\t evaluator", i, "down")
workers.Done()
}(i)
//l.Println("\t evaluator", i, "started")
}
// Start the tasks
taskList := make([]*multTask, 0)
l.Println("> Eval Phase")
elapsedEvalCloud = runTimed(func() {
for i, lvl := range encLvls[:len(encLvls)-1] {
nextLvl := encLvls[i+1]
l.Println("\tlevel", i, len(lvl), "->", len(nextLvl))
wg := &sync.WaitGroup{}
wg.Add(len(nextLvl))
for j, nextLvlCt := range nextLvl {
task := multTask{wg, lvl[2*j], lvl[2*j+1], nextLvlCt, 0}
taskList = append(taskList, &task)
tasks <- &task
}
wg.Wait()
}
})
elapsedEvalCloudCPU = time.Duration(0)
for _, t := range taskList {
elapsedEvalCloudCPU += t.elapsedmultTask
}
elapsedEvalParty = time.Duration(0)
l.Printf("\tdone (cloud: %s (wall: %s), party: %s)\n",
elapsedEvalCloudCPU, elapsedEvalCloud, elapsedEvalParty)
//l.Println("> Shutting down workers")
close(tasks)
workers.Wait()
return
}
func genparties(params heint.Parameters, N int) []*party {
// Create each party, and allocate the memory for all the shares that the protocols will need
P := make([]*party, N)
for i := range P {
pi := &party{}
pi.sk = rlwe.NewKeyGenerator(params).GenSecretKeyNew()
P[i] = pi
}
return P
}
func genInputs(params heint.Parameters, P []*party) (expRes []uint64) {
expRes = make([]uint64, params.N())
for i := range expRes {
expRes[i] = 1
}
for _, pi := range P {
pi.input = make([]uint64, params.N())
for i := range pi.input {
if sampling.RandFloat64(0, 1) > 0.3 || i == 4 {
pi.input[i] = 1
}
expRes[i] *= pi.input[i]
}
}
return
}
func pcksPhase(params heint.Parameters, tpk *rlwe.PublicKey, encRes *rlwe.Ciphertext, P []*party) (encOut *rlwe.Ciphertext) {
l := log.New(os.Stderr, "", 0)
// Collective key switching from the collective secret key to
// the target public key
pcks, err := mhe.NewPublicKeySwitchProtocol(params, ring.DiscreteGaussian{Sigma: 1 << 30, Bound: 6 * (1 << 30)})
if err != nil {
panic(err)
}
for _, pi := range P {
pi.pcksShare = pcks.AllocateShare(params.MaxLevel())
}
l.Println("> PublicKeySwitch Phase")
elapsedPCKSParty = runTimedParty(func() {
for _, pi := range P {
/* #nosec G601 -- Implicit memory aliasing in for loop acknowledged */
pcks.GenShare(pi.sk, tpk, encRes, &pi.pcksShare)
}
}, len(P))
pcksCombined := pcks.AllocateShare(params.MaxLevel())
encOut = heint.NewCiphertext(params, 1, params.MaxLevel())
elapsedPCKSCloud = runTimed(func() {
for _, pi := range P {
if err = pcks.AggregateShares(pi.pcksShare, pcksCombined, &pcksCombined); err != nil {
panic(err)
}
}
pcks.KeySwitch(encRes, pcksCombined, encOut)
})
l.Printf("\tdone (cloud: %s, party: %s)\n", elapsedPCKSCloud, elapsedPCKSParty)
return
}
func rkgphase(params heint.Parameters, crs sampling.PRNG, P []*party) *rlwe.RelinearizationKey {
l := log.New(os.Stderr, "", 0)
l.Println("> RelinearizationKeyGen Phase")
rkg := mhe.NewRelinearizationKeyGenProtocol(params) // Relineariation key generation
_, rkgCombined1, rkgCombined2 := rkg.AllocateShare()
for _, pi := range P {
pi.rlkEphemSk, pi.rkgShareOne, pi.rkgShareTwo = rkg.AllocateShare()
}
crp := rkg.SampleCRP(crs)
elapsedRKGParty = runTimedParty(func() {
for _, pi := range P {
/* #nosec G601 -- Implicit memory aliasing in for loop acknowledged */
rkg.GenShareRoundOne(pi.sk, crp, pi.rlkEphemSk, &pi.rkgShareOne)
}
}, len(P))
elapsedRKGCloud = runTimed(func() {
for _, pi := range P {
/* #nosec G601 -- Implicit memory aliasing in for loop acknowledged */
rkg.AggregateShares(pi.rkgShareOne, rkgCombined1, &rkgCombined1)
}
})
elapsedRKGParty += runTimedParty(func() {
for _, pi := range P {
/* #nosec G601 -- Implicit memory aliasing in for loop acknowledged */
rkg.GenShareRoundTwo(pi.rlkEphemSk, pi.sk, rkgCombined1, &pi.rkgShareTwo)
}
}, len(P))
rlk := rlwe.NewRelinearizationKey(params)
elapsedRKGCloud += runTimed(func() {
for _, pi := range P {
/* #nosec G601 -- Implicit memory aliasing in for loop acknowledged */
rkg.AggregateShares(pi.rkgShareTwo, rkgCombined2, &rkgCombined2)
}
rkg.GenRelinearizationKey(rkgCombined1, rkgCombined2, rlk)
})
l.Printf("\tdone (cloud: %s, party: %s)\n", elapsedRKGCloud, elapsedRKGParty)
return rlk
}
func ckgphase(params heint.Parameters, crs sampling.PRNG, P []*party) *rlwe.PublicKey {
l := log.New(os.Stderr, "", 0)
l.Println("> PublicKeyGen Phase")
ckg := mhe.NewPublicKeyGenProtocol(params) // Public key generation
ckgCombined := ckg.AllocateShare()
for _, pi := range P {
pi.ckgShare = ckg.AllocateShare()
}
crp := ckg.SampleCRP(crs)
elapsedCKGParty = runTimedParty(func() {
for _, pi := range P {
/* #nosec G601 -- Implicit memory aliasing in for loop acknowledged */
ckg.GenShare(pi.sk, crp, &pi.ckgShare)
}
}, len(P))
pk := rlwe.NewPublicKey(params)
elapsedCKGCloud = runTimed(func() {
for _, pi := range P {
ckg.AggregateShares(pi.ckgShare, ckgCombined, &ckgCombined)
}
ckg.GenPublicKey(ckgCombined, crp, pk)
})
l.Printf("\tdone (cloud: %s, party: %s)\n", elapsedCKGCloud, elapsedCKGParty)
return pk
}