mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
429 lines
11 KiB
Go
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
|
|
}
|