├── .vscode └── settings.json ├── logo.png ├── Project.toml ├── src ├── ring │ ├── arithmetic.jl │ ├── sampler.jl │ ├── polynomial.jl │ └── fft.jl ├── MKTFHE.jl ├── tfhe │ ├── gate.jl │ ├── params.jl │ ├── keygen.jl │ ├── scheme.jl │ └── bootstrapping.jl └── ciphertext │ ├── key.jl │ ├── unienc.jl │ ├── lev.jl │ ├── lwe.jl │ └── gsw.jl ├── test ├── CGGI.jl ├── LMSS.jl ├── CCS.jl ├── KMS.jl └── KMSblock.jl ├── README.md └── Manifest.toml /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SNUCP/MKTFHE/HEAD/logo.png -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "MKTFHE" 2 | uuid = "21a44bb3-4525-4507-8d80-2033ffe07ff1" 3 | authors = ["Seonhong Min (Luke Min) "] 4 | version = "1.0.0" 5 | 6 | [deps] 7 | MultiFloats = "bdf0d083-296b-4888-a5b6-7498122e68a5" 8 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 9 | -------------------------------------------------------------------------------- /src/ring/arithmetic.jl: -------------------------------------------------------------------------------- 1 | @inline native(x::Float64, mask::UInt32) = begin 2 | x -= floor(x * 2.3283064365386963e-10) * 4.294967296e9 3 | x == 4.294967296e9 ? UInt32(0) : trunc(UInt32, x) 4 | end 5 | 6 | @inline native(x::Float64, mask::UInt64)::UInt64 = begin 7 | x -= floor(x * 5.421010862427522e-20) * 1.8446744073709552e19 8 | x == 1.8446744073709552e19 ? UInt64(0) : trunc(UInt64, x) 9 | end 10 | 11 | @inline native(x::MultiFloat{Float64, l}, mask::T) where {T<:Unsigned, l} = begin 12 | res = zero(T) 13 | @inbounds for i = 1 : l 14 | res += native(x._limbs[i], mask) 15 | end 16 | res 17 | end 18 | 19 | to_int(a::Unsigned) = signed(a) 20 | 21 | @inline bits(T::Type) = T == UInt32 ? 32 : 64 22 | 23 | @inline divbits(a::T, bit::Int64) where {T<:Unsigned} = begin 24 | maxbit = bits(T) 25 | carry = (a << (maxbit - bit)) >> (maxbit - 1) 26 | a >> bit + carry 27 | end 28 | -------------------------------------------------------------------------------- /src/ring/sampler.jl: -------------------------------------------------------------------------------- 1 | uniform_binary(N::Int64, T::Type) = 2 | rand(ChaCha20Stream(), [T(0), T(1)], N) 3 | 4 | uniform_ternary(N::Int64, T::Type) = 5 | rand(ChaCha20Stream(), [-T(1), T(0), T(1)], N) 6 | 7 | function block_binary(d::Int64, ℓ::Int64, T::Type) 8 | vec = Vector{T}(undef, d * ℓ) 9 | @inbounds @simd for i = 1 : d * ℓ 10 | vec[i] = T(0) 11 | end 12 | 13 | rng = ChaCha20Stream() 14 | 15 | @inbounds @simd for i = 0 : d - 1 16 | idx = rand(rng, 0 : ℓ) 17 | if idx != 0 18 | vec[i * ℓ + idx] = T(1) 19 | end 20 | end 21 | vec 22 | end 23 | 24 | gaussian(σ::Float64) = 25 | σ * randn(ChaCha20Stream(), Float64) 26 | 27 | gaussian(N::Int64, σ::Float64) = 28 | σ * randn(ChaCha20Stream(), Float64, N) 29 | 30 | uniform_random32(N::Int64) = 31 | rand(ChaCha20Stream(), UInt32, N) 32 | 33 | uniform_random64(N::Int64) = 34 | rand(ChaCha20Stream(), UInt64, N) 35 | -------------------------------------------------------------------------------- /src/MKTFHE.jl: -------------------------------------------------------------------------------- 1 | module MKTFHE 2 | 3 | using ChaChaCiphers: ChaCha20Stream 4 | using Base.Threads: @threads, @spawn, @sync 5 | using MultiFloats 6 | # MultiFloats package is the state-of-the-art multi-precision Julia package. 7 | 8 | include("ring/arithmetic.jl") 9 | include("ring/polynomial.jl") 10 | include("ring/sampler.jl") 11 | include("ring/fft.jl") 12 | 13 | include("ciphertext/key.jl") 14 | include("ciphertext/lwe.jl") 15 | include("ciphertext/lev.jl") 16 | include("ciphertext/gsw.jl") 17 | include("ciphertext/unienc.jl") 18 | 19 | include("tfhe/keygen.jl") 20 | include("tfhe/scheme.jl") 21 | export TFHEparams_bin, TFHEparams_block, CCSparams, KMSparams 22 | export CGGI, LMSS, CCS, KMS 23 | export setup, party_keygen, lwe_encrypt, lwe_decrypt, lwe_ith_encrypt, CRS 24 | 25 | include("tfhe/bootstrapping.jl") 26 | export bootstrapping! 27 | 28 | include("tfhe/gate.jl") 29 | export NAND, AND, OR, XOR, XNOR, NOR, NOT! 30 | 31 | include("tfhe/params.jl") 32 | export CGGIparam, Blockparam 33 | export CCS2party, CCS4party, CCS8party, CCS16party 34 | export KMS2party, KMS4party, KMS8party, KMS16party, KMS32party 35 | export KMS2partyblock, KMS4partyblock, KMS8partyblock, KMS16partyblock, KMS32partyblock 36 | 37 | end -------------------------------------------------------------------------------- /test/CGGI.jl: -------------------------------------------------------------------------------- 1 | include("../src/MKTFHE.jl") 2 | using .MKTFHE, Printf 3 | 4 | function main() 5 | params = CGGIparam 6 | 7 | @printf("KEY GENERATION ...\n") 8 | 9 | lwekey, ringkey, scheme = setup(params) 10 | 11 | getsize(var) = Base.format_bytes(Base.summarysize(var)) 12 | @printf("BRK SIZE : %s, KSK SIZE : %s\n\n", 13 | getsize(scheme.btk.brk), getsize(scheme.btk.ksk)) 14 | 15 | gates = [(NAND, (x, y) -> x⊼y, "⊼ "), (AND, (x, y) -> x&y, "& "), (OR, (x, y) -> x||y, "|| "), (XOR, (x, y) -> x⊻y, "⊻ "), (XNOR, (x, y) -> !(x⊻y), "XNOR "), (NOR, (x, y) -> x⊽y, "NOR ")] 16 | 17 | for idx = 1 : 5 18 | m = rand(Bool, 4) 19 | ctxts = map(i -> lwe_encrypt(m[i], lwekey, params), 1:4) 20 | res = ctxts[1] 21 | mres = m[1] 22 | circuit = "m1 " 23 | 24 | @time begin 25 | for i = 2 : 4 26 | randgate = rand(gates) 27 | res = randgate[1](res, ctxts[i], scheme) 28 | mres = randgate[2](mres, m[i]) 29 | circuit *= randgate[3] * "m$i " 30 | end 31 | end 32 | 33 | bootstrapping!(res, scheme) 34 | @assert mres == lwe_decrypt(res, lwekey) 35 | println("Trial $idx : $circuit= $mres") 36 | end 37 | end 38 | 39 | main() -------------------------------------------------------------------------------- /test/LMSS.jl: -------------------------------------------------------------------------------- 1 | include("../src/MKTFHE.jl") 2 | using .MKTFHE, Printf 3 | 4 | function main() 5 | params = Blockparam 6 | 7 | @printf("KEY GENERATION ...\n") 8 | 9 | lwekey, ringkey, scheme = setup(params) 10 | 11 | getsize(var) = Base.format_bytes(Base.summarysize(var)) 12 | @printf("BRK SIZE : %s, KSK SIZE : %s\n\n", 13 | getsize(scheme.btk.brk), getsize(scheme.btk.ksk)) 14 | 15 | gates = [(NAND, (x, y) -> x⊼y, "⊼ "), (AND, (x, y) -> x&y, "& "), (OR, (x, y) -> x||y, "|| "), (XOR, (x, y) -> x⊻y, "⊻ "), (XNOR, (x, y) -> !(x⊻y), "XNOR "), (NOR, (x, y) -> x⊽y, "NOR ")] 16 | 17 | for idx = 1 : 5 18 | m = rand(Bool, 4) 19 | ctxts = map(i -> lwe_encrypt(m[i], lwekey, params), 1:4) 20 | res = ctxts[1] 21 | mres = m[1] 22 | circuit = "m1 " 23 | 24 | @time begin 25 | for i = 2 : 4 26 | randgate = rand(gates) 27 | res = randgate[1](res, ctxts[i], scheme) 28 | mres = randgate[2](mres, m[i]) 29 | circuit *= randgate[3] * "m$i " 30 | end 31 | end 32 | 33 | bootstrapping!(res, scheme) 34 | @assert mres == lwe_decrypt(res, lwekey) 35 | println("Trial $idx : $circuit= $mres") 36 | end 37 | end 38 | 39 | main() -------------------------------------------------------------------------------- /test/CCS.jl: -------------------------------------------------------------------------------- 1 | include("../src/MKTFHE.jl") 2 | using .MKTFHE, Printf 3 | 4 | function main() 5 | params = CCS2party 6 | a = CRS(params) 7 | 8 | @printf("KEY GENERATION ...\n") 9 | 10 | keys = [party_keygen(a, params) for _ = 1 : params.k] 11 | lwekeys = first.(keys) 12 | btk = last.(keys) 13 | 14 | getsize(var) = Base.format_bytes(Base.summarysize(var)) 15 | @printf("BRK SIZE : %s, KSK SIZE : %s\n\n", 16 | getsize(btk[1].brk), getsize(btk[1].ksk)) 17 | 18 | scheme = setup(a, btk, params) 19 | 20 | gates = [(NAND, (x, y) -> x⊼y, "⊼ "), (AND, (x, y) -> x&y, "& "), (OR, (x, y) -> x||y, "|| "), (XOR, (x, y) -> x⊻y, "⊻ "), (XNOR, (x, y) -> !(x⊻y), "XNOR "), (NOR, (x, y) -> x⊽y, "NOR ")] 21 | 22 | for idx = 1 : 5 23 | m = rand(Bool, params.k) 24 | ctxts = map(i -> lwe_ith_encrypt(m[i], i, lwekeys[i], params), 1 : params.k) 25 | res = ctxts[1] 26 | mres = m[1] 27 | circuit = "m1 " 28 | 29 | for i = 2 : params.k 30 | randgate = rand(gates) 31 | res = randgate[1](res, ctxts[i], scheme) 32 | mres = randgate[2](mres, m[i]) 33 | circuit *= randgate[3] * "m$i " 34 | end 35 | 36 | @time bootstrapping!(res, scheme) 37 | @assert mres == lwe_decrypt(res, lwekeys, params) 38 | println("Trial $idx : $circuit= $mres") 39 | end 40 | end 41 | 42 | main() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MKTFHE 2 | 3 |

4 | 5 |

6 | 7 | This implementation is a proof-of-concept for new multi-key TFHE scheme (https://eprint.iacr.org/2022/1460). 8 | 9 | For the sake of performance, Float64 is used instead of multi precision floating number when multiplying polynomials with 64 bit coefficients, but the option can be manually turned on when defining the parameters. 10 | 11 | Before you run the code, please make sure to install the following packages : ChaChaCiphers, MultiFloats. 12 | To install them, you can open the REPL and type the following commands. 13 | 14 |
15 | 
16 | ]
17 | add ChaChaCiphers
18 | add MultiFloats
19 | 
20 | 
21 | 22 | To run the test code for CCS, type the following commands in the terminal. 23 | 24 |
25 | 
26 | julia ./test/CCS.jl
27 | 
28 | 
29 | 30 | To run the test code for our MK-TFHE scheme, type the following commands in the terminal. 31 | 32 |
33 | 
34 | julia ./test/KMS.jl
35 | 
36 | 
37 | 38 | To run the test code for the parallelized version of our MK-TFHE scheme, type the following commands in the terminal. 39 | 40 |
41 | 
42 | julia --threads=auto ./test/KMS.jl
43 | 
44 | 
45 | 46 | This code also provides the implementation of CGGI scheme and LMSS(Faster TFHE bootstrapping from Block Binary Distribution) scheme. 47 | -------------------------------------------------------------------------------- /test/KMS.jl: -------------------------------------------------------------------------------- 1 | include("../src/MKTFHE.jl") 2 | using .MKTFHE, Printf 3 | 4 | function main() 5 | params = KMS2party 6 | a = CRS(params) 7 | 8 | @printf("KEY GENERATION ...\n") 9 | 10 | keys = [party_keygen(a, params) for _ = 1 : params.k] 11 | lwekeys = first.(keys) 12 | btk = last.(keys) 13 | 14 | getsize(var) = Base.format_bytes(Base.summarysize(var)) 15 | @printf("BRK SIZE : %s, RLK SIZE : %s, KSK SIZE : %s\n\n", 16 | getsize(btk[1].brk), getsize(btk[1].rlk), getsize(btk[1].ksk)) 17 | 18 | scheme = setup(a, btk, params) 19 | 20 | gates = [(NAND, (x, y) -> x⊼y, "⊼ "), (AND, (x, y) -> x&y, "& "), (OR, (x, y) -> x||y, "|| "), (XOR, (x, y) -> x⊻y, "⊻ "), (XNOR, (x, y) -> !(x⊻y), "XNOR "), (NOR, (x, y) -> x⊽y, "NOR ")] 21 | 22 | for idx = 1 : 5 23 | m = rand(Bool, params.k) 24 | ctxts = map(i -> lwe_ith_encrypt(m[i], i, lwekeys[i], params), 1 : params.k) 25 | res = ctxts[1] 26 | mres = m[1] 27 | circuit = "m1 " 28 | 29 | for i = 2 : params.k 30 | randgate = rand(gates) 31 | res = randgate[1](res, ctxts[i], scheme) 32 | mres = randgate[2](mres, m[i]) 33 | circuit *= randgate[3] * "m$i " 34 | end 35 | 36 | @time bootstrapping!(res, scheme) 37 | @assert mres == lwe_decrypt(res, lwekeys, params) 38 | println("Trial $idx : $circuit= $mres") 39 | end 40 | end 41 | 42 | main() 43 | -------------------------------------------------------------------------------- /test/KMSblock.jl: -------------------------------------------------------------------------------- 1 | include("../src/MKTFHE.jl") 2 | using .MKTFHE, Printf 3 | 4 | function main() 5 | params = KMS2partyblock 6 | a = CRS(params) 7 | 8 | @printf("KEY GENERATION ...\n") 9 | 10 | keys = [party_keygen(a, params) for _ = 1 : params.k] 11 | lwekeys = first.(keys) 12 | btk = last.(keys) 13 | 14 | getsize(var) = Base.format_bytes(Base.summarysize(var)) 15 | @printf("BRK SIZE : %s, RLK SIZE : %s, KSK SIZE : %s\n\n", 16 | getsize(btk[1].brk), getsize(btk[1].rlk), getsize(btk[1].ksk)) 17 | 18 | scheme = setup(a, btk, params) 19 | 20 | gates = [(NAND, (x, y) -> x⊼y, "⊼ "), (AND, (x, y) -> x&y, "& "), (OR, (x, y) -> x||y, "|| "), (XOR, (x, y) -> x⊻y, "⊻ "), (XNOR, (x, y) -> !(x⊻y), "XNOR "), (NOR, (x, y) -> x⊽y, "NOR ")] 21 | 22 | for idx = 1 : 5 23 | m = rand(Bool, params.k) 24 | ctxts = map(i -> lwe_ith_encrypt(m[i], i, lwekeys[i], params), 1 : params.k) 25 | res = ctxts[1] 26 | mres = m[1] 27 | circuit = "m1 " 28 | 29 | for i = 2 : params.k 30 | randgate = rand(gates) 31 | res = randgate[1](res, ctxts[i], scheme) 32 | mres = randgate[2](mres, m[i]) 33 | circuit *= randgate[3] * "m$i " 34 | end 35 | 36 | @time bootstrapping!(res, scheme) 37 | @assert mres == lwe_decrypt(res, lwekeys, params) 38 | println("Trial $idx : $circuit= $mres") 39 | end 40 | end 41 | 42 | main() -------------------------------------------------------------------------------- /Manifest.toml: -------------------------------------------------------------------------------- 1 | # This file is machine-generated - editing it directly is not advised 2 | 3 | julia_version = "1.9.0" 4 | manifest_format = "2.0" 5 | project_hash = "f46bed2718fc20ced71ec29c2d6526fae9d064a7" 6 | 7 | [[deps.Artifacts]] 8 | uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" 9 | 10 | [[deps.CompilerSupportLibraries_jll]] 11 | deps = ["Artifacts", "Libdl"] 12 | uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" 13 | version = "1.0.2+0" 14 | 15 | [[deps.Libdl]] 16 | uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" 17 | 18 | [[deps.LinearAlgebra]] 19 | deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] 20 | uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 21 | 22 | [[deps.MultiFloats]] 23 | deps = ["LinearAlgebra", "Printf", "Random"] 24 | git-tree-sha1 = "6d51baf986bbba9ec681a0f26aac510694fc4b98" 25 | uuid = "bdf0d083-296b-4888-a5b6-7498122e68a5" 26 | version = "1.0.3" 27 | 28 | [[deps.OpenBLAS_jll]] 29 | deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] 30 | uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" 31 | version = "0.3.21+4" 32 | 33 | [[deps.Printf]] 34 | deps = ["Unicode"] 35 | uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" 36 | 37 | [[deps.Random]] 38 | deps = ["SHA", "Serialization"] 39 | uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 40 | 41 | [[deps.SHA]] 42 | uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" 43 | version = "0.7.0" 44 | 45 | [[deps.Serialization]] 46 | uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" 47 | 48 | [[deps.Unicode]] 49 | uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" 50 | 51 | [[deps.libblastrampoline_jll]] 52 | deps = ["Artifacts", "Libdl"] 53 | uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" 54 | version = "5.7.0+0" 55 | -------------------------------------------------------------------------------- /src/tfhe/gate.jl: -------------------------------------------------------------------------------- 1 | function NAND(ctxt1::LWE{T}, ctxt2::LWE{T}, scheme::TFHEscheme) where T 2 | b = T(1) << (bits(T) - 3) - ctxt1.b - ctxt2.b 3 | a = zeros(T, length(ctxt1.a)) 4 | @. a = - ctxt1.a - ctxt2.a 5 | res = LWE(b, a) 6 | bootstrapping!(res, scheme) 7 | res 8 | end 9 | 10 | function AND(ctxt1::LWE{T}, ctxt2::LWE{T}, scheme::TFHEscheme) where T 11 | b = T(7) << (bits(T) - 3) + ctxt1.b + ctxt2.b 12 | a = zeros(T, length(ctxt1.a)) 13 | @. a = ctxt1.a + ctxt2.a 14 | res = LWE(b, a) 15 | bootstrapping!(res, scheme) 16 | res 17 | end 18 | 19 | function OR(ctxt1::LWE{T}, ctxt2::LWE{T}, scheme::TFHEscheme) where T 20 | b = T(1) << (bits(T) - 3) + ctxt1.b + ctxt2.b 21 | a = zeros(T, length(ctxt1.a)) 22 | @. a = ctxt1.a + ctxt2.a 23 | res = LWE(b, a) 24 | bootstrapping!(res, scheme) 25 | res 26 | end 27 | 28 | function XOR(ctxt1::LWE{T}, ctxt2::LWE{T}, scheme::TFHEscheme) where T 29 | b = T(1) << (bits(T) - 2) + T(2) * (ctxt1.b + ctxt2.b) 30 | a = zeros(T, length(ctxt1.a)) 31 | @. a = T(2) * (ctxt1.a + ctxt2.a) 32 | res = LWE(b, a) 33 | bootstrapping!(res, scheme) 34 | res 35 | end 36 | 37 | function XNOR(ctxt1::LWE{T}, ctxt2::LWE{T}, scheme::TFHEscheme) where T 38 | b = T(3) << (bits(T) - 2) - T(2) * (ctxt1.b + ctxt2.b) 39 | a = zeros(T, length(ctxt1.a)) 40 | @. a = -T(2) * (ctxt1.a + ctxt2.a) 41 | res = LWE(b, a) 42 | bootstrapping!(res, scheme) 43 | res 44 | end 45 | 46 | function NOR(ctxt1::LWE{T}, ctxt2::LWE{T}, scheme::TFHEscheme) where T 47 | b = T(7) << (bits(T) - 3) - ctxt1.b - ctxt2.b 48 | a = zeros(T, length(ctxt1.a)) 49 | @. a = - ctxt1.a - ctxt2.a 50 | res = LWE(b, a) 51 | bootstrapping!(res, scheme) 52 | res 53 | end 54 | 55 | function NOT!(ctxt::LWE{T}) where T 56 | ctxt.b *= -T(1) 57 | @. ctxt.a *= -T(1) 58 | end -------------------------------------------------------------------------------- /src/tfhe/params.jl: -------------------------------------------------------------------------------- 1 | CGGIparam = TFHEparams_bin{UInt32, Float64x2, Float64}( 2 | 630, 1 << 17, 3 | 8, 2, 4 | 1 << 10, 1, 1 << 7, 5 | 3, 9 6 | ) 7 | 8 | Blockparam = TFHEparams_block{UInt32, Float64x2, Float64}( 9 | 229, 3, 1 << 17, 10 | 8, 2, 11 | 1 << 10, 1, 1 << 7, 12 | 3, 9 13 | ) 14 | 15 | CCS2party = CCSparams{UInt32, Float64x2, Float64}( 16 | 560, 1 << 17, 17 | 8, 2, 18 | 1 << 10, 1 << 4, 19 | 3, 8, 20 | 2 21 | ) 22 | 23 | CCS4party = CCSparams{UInt32, Float64x2, Float64}( 24 | 560, 1 << 17, 25 | 8, 2, 26 | 1 << 10, 1 << 4, 27 | 4, 8, 28 | 4 29 | ) 30 | 31 | CCS8party = CCSparams{UInt32, Float64x2, Float64}( 32 | 560, 1 << 17, 33 | 8, 2, 34 | 1 << 10, 1 << 4, 35 | 5, 6, 36 | 8 37 | ) 38 | 39 | CCS16party = CCSparams{UInt32, Float64x2, Float64}( 40 | 560, 1 << 17, 41 | 8, 2, 42 | 1 << 10, 1 << 4, 43 | 12, 2, 44 | 16 45 | ) 46 | 47 | KMS2party = KMSparams{UInt32, UInt64, Float64x2, Float64}( 48 | 560, 1 << 17, 49 | 8, 2, 50 | 1 << 11, 85.4084, 51 | 3, 12, 2, 7, 3, 10, 52 | 2 53 | ) 54 | 55 | KMS4party = KMSparams{UInt32, UInt64, Float64x2, Float64}( 56 | 560, 1 << 17, 57 | 8, 2, 58 | 1 << 11, 85.4084, 59 | 5, 8, 2, 8, 7, 6, 60 | 4 61 | ) 62 | 63 | KMS8party = KMSparams{UInt32, UInt64, Float64x2, Float64}( 64 | 560, 1 << 17, 65 | 8, 2, 66 | 1 << 11, 85.4084, 67 | 4, 9, 3, 6, 8, 4, 68 | 8 69 | ) 70 | 71 | KMS16party = KMSparams{UInt32, UInt64, Float64x2, Float64}( 72 | 560, 1 << 17, 73 | 8, 2, 74 | 1 << 11, 85.4084, 75 | 5, 8, 3, 6, 9, 4, 76 | 16 77 | ) 78 | 79 | KMS32party = KMSparams{UInt32, UInt64, Float64x2, Float64}( 80 | 560, 1 << 17, 81 | 8, 2, 82 | 1 << 11, 85.4084, 83 | 6, 7, 3, 7, 16, 2, 84 | 32 85 | ) 86 | 87 | KMS2partyblock = KMSparams_block{UInt32, UInt64, Float64x2, Float64}( 88 | 203, 3, 1 << 17, 89 | 8, 2, 90 | 1 << 11, 85.4084, 91 | 3, 12, 2, 7, 3, 10, 92 | 2 93 | ) 94 | 95 | KMS4partyblock = KMSparams_block{UInt32, UInt64, Float64x2, Float64}( 96 | 203, 3, 1 << 17, 97 | 8, 2, 98 | 1 << 11, 85.4084, 99 | 5, 8, 2, 8, 7, 6, 100 | 4 101 | ) 102 | 103 | KMS8partyblock = KMSparams_block{UInt32, UInt64, Float64x2, Float64}( 104 | 203, 3, 1 << 17, 105 | 8, 2, 106 | 1 << 11, 85.4084, 107 | 4, 9, 3, 6, 8, 4, 108 | 8 109 | ) 110 | 111 | KMS16partyblock = KMSparams_block{UInt32, UInt64, Float64x2, Float64}( 112 | 203, 3, 1 << 17, 113 | 8, 2, 114 | 1 << 11, 85.4084, 115 | 5, 8, 3, 6, 9, 4, 116 | 16 117 | ) 118 | 119 | KMS32partyblock = KMSparams_block{UInt32, UInt64, Float64x2, Float64}( 120 | 203, 3, 1 << 17, 121 | 8, 2, 122 | 1 << 11, 85.4084, 123 | 6, 7, 3, 7, 16, 2, 124 | 32 125 | ) -------------------------------------------------------------------------------- /src/ciphertext/key.jl: -------------------------------------------------------------------------------- 1 | struct LWEkey{T<:Unsigned} 2 | n::Int64 3 | key::Vector{T} 4 | 5 | LWEkey(n::Int64, key::Vector{T}) where {T<:Unsigned} = 6 | new{T}(n, key) 7 | end 8 | 9 | LEVkey = LWEkey 10 | GSWkey = LWEkey 11 | 12 | binary_lwekey(n::Int64, T::Type) = 13 | LWEkey(n, uniform_binary(n, T)) 14 | 15 | ternary_lwekey(n::Int64, T::Type) = 16 | LWEkey(n, uniform_ternary(n, T)) 17 | 18 | block_binary_lwekey(d::Int64, ℓ::Int64, T::Type) = 19 | LWEkey(d * ℓ, block_binary(d, ℓ, T)) 20 | 21 | struct RLWEkey{T, R} 22 | k::Int64 23 | N::Int64 24 | key::Vector{<:RingPoly{T}} 25 | tkey::Vector{<:TransPoly{R}} 26 | end 27 | 28 | RLEVkey = RLWEkey 29 | RGSWkey = RLWEkey 30 | Unikey = RLWEkey 31 | 32 | binary_ringkey(k::Int64, N::Int64, T::Type, ffter::FFTransformer{R}) where R = begin 33 | key = Vector{NativePoly{T}}(undef, k) 34 | tkey = Vector{TransNativePoly{R}}(undef, k) 35 | @inbounds @simd for i = 1 : k 36 | key[i] = NativePoly{T}(uniform_binary(N, T), N) 37 | tkey[i] = fft(key[i], ffter) 38 | end 39 | RLWEkey{T, R}(k, N, key, tkey) 40 | end 41 | 42 | ternary_ringkey(k::Int64, N::Int64, T::Type, ffter::FFTransformer{R}) where R = begin 43 | key = Vector{NativePoly{T}}(undef, k) 44 | tkey = Vector{TransNativePoly{R}}(undef, k) 45 | @inbounds @simd for i = 1 : k 46 | key[i] = NativePoly{T}(uniform_ternary(N, T), N) 47 | tkey[i] = fft(key[i], ffter) 48 | end 49 | RLWEkey{T, R}(k, N, key, tkey) 50 | end 51 | 52 | function partial_ringkey(k::Int64, N::Int64, lwekey::LWEkey{T}, ffter::FFTransformer{R}) where {T, R} 53 | n = lwekey.n 54 | key = Vector{NativePoly{T}}(undef, k) 55 | tkey = Vector{TransNativePoly{R}}(undef, k) 56 | @inbounds @simd for i = 1 : k 57 | if n ≥ N 58 | key[i] = NativePoly{T}(lwekey.key[(i-1)*N+1:i*N], N) 59 | n -= N 60 | elseif n ≥ 0 61 | key[i] = NativePoly{T}(vcat(lwekey.key[(i-1)*N+1:end], uniform_binary(N-n, T)), N) 62 | n -= N 63 | else 64 | key[i] = NativePoly{T}(uniform_binary(N, T), N) 65 | end 66 | tkey[i] = fft(key[i], ffter) 67 | end 68 | RLWEkey{T, R}(k, N, key, tkey) 69 | end 70 | 71 | function partial_ringkey(k::Int64, N::Int64, S::Type, lwekey::LWEkey{T}, ffter::FFTransformer{R}) where {T, R} 72 | n = lwekey.n 73 | key = Vector{NativePoly{S}}(undef, k) 74 | tkey = Vector{TransNativePoly{R}}(undef, k) 75 | @inbounds @simd for i = 1 : k 76 | if n ≥ N 77 | key[i] = NativePoly{S}(S.(lwekey.key[(i-1)*N+1:i*N]), N) 78 | n -= N 79 | elseif n ≥ 0 80 | key[i] = NativePoly{S}(vcat(S.(lwekey.key[(i-1)*N+1:end]), uniform_binary(N-n, S)), N) 81 | n -= N 82 | else 83 | key[i] = NativePoly{S}(uniform_binary(N, S), N) 84 | end 85 | tkey[i] = fft(key[i], ffter) 86 | end 87 | RLWEkey{S, R}(k, N, key, tkey) 88 | end -------------------------------------------------------------------------------- /src/ciphertext/unienc.jl: -------------------------------------------------------------------------------- 1 | Uniparams{T} = GSWparams{T} 2 | Uniparams_digit{T} = GSWparams_digit{T} 3 | 4 | function decomptoith!(avec::Array{NativePoly{T}, 2}, a::Vector{NativePoly{T}}, i::Int64, params::Union{GSWparams_digit{T}, LEVparams_digit{T}}) where T 5 | @inbounds @simd for idx = 1 : i 6 | @inbounds @simd for j1 = 1 : a[1].N 7 | aj1 = divbits(a[idx].coeffs[j1], params.gveclog[end]) 8 | @inbounds for j2 = params.l : -1 : 1 9 | tmp = aj1 & params.mask 10 | aj1 >>= params.logB 11 | carry = tmp >> (params.logB - 1) 12 | aj1 += carry 13 | tmp -= carry << params.logB 14 | avec[j2, idx].coeffs[j1] = tmp 15 | end 16 | end 17 | end 18 | end 19 | 20 | CRS{T} = Vector{<:RingPoly{T}} 21 | TransCRS{T} = Vector{<:TransPoly{T}} 22 | 23 | fft(a::CRS, ffter::FFTransformer{T}) where T = 24 | fft.(a, Ref(ffter)) 25 | 26 | # UniEnc is not mutable, since you cannot generate a valid uni-encryption by homomorphic operations. 27 | struct UniEnc{T<:Unsigned} 28 | d::Vector{RingPoly{T}} 29 | f::RLEV{T} 30 | 31 | function UniEnc(d::Vector{<:RingPoly{T}}, f::RLEV{T}) where {T<:Unsigned} 32 | new{T}(d, f) 33 | end 34 | end 35 | 36 | function unienc_encrypt(ta::TransCRS{S}, m::T, key::Unikey{T}, σ::Float64, ffter::FFTransformer{S}, params::Uniparams{T}) where {T, S} 37 | N = key.N 38 | r = ternary_ringkey(1, N, T, ffter) 39 | 40 | d = Vector{NativePoly{T}}(undef, params.l) 41 | tdi = bufftransnativepoly(N, S) 42 | 43 | @inbounds for i = 1 : params.l 44 | multo!(tdi, ta[i], r.tkey[1]) 45 | d[i] = ifft(tdi, ffter) 46 | d[i].coeffs[1] += m * params.gvec[i] 47 | @inbounds @simd for j = 1 : key.N 48 | d[i].coeffs[j] += unsigned(round.(signed(T), gaussian(σ))) 49 | end 50 | end 51 | 52 | f = rlev_encrypt(r.key[1], key, σ, ffter, params) 53 | 54 | UniEnc(d, f) 55 | end 56 | 57 | function unienc_encrypt(ta::TransCRS{S}, m::NativePoly{T}, key::Unikey{T}, σ::Float64, ffter::FFTransformer{S}, params::Uniparams{T}) where {T, S} 58 | N = key.N 59 | r = ternary_ringkey(1, N, T, ffter) 60 | 61 | d = Vector{NativePoly{T}}(undef, params.l) 62 | tdi = bufftransnativepoly(N, S) 63 | 64 | @inbounds for i = 1 : params.l 65 | multo!(tdi, ta[i], r.tkey[1]) 66 | d[i] = ifft(tdi, ffter) 67 | @inbounds @simd for j = 1 : key.N 68 | d[i].coeffs[j] += m.coeffs[j] * params.gvec[i] + unsigned(round.(signed(T), gaussian(σ))) 69 | end 70 | end 71 | 72 | f = rlev_encrypt(r.key[1], key, σ, ffter, params) 73 | 74 | UniEnc(d, f) 75 | end 76 | 77 | function gen_b(ta::TransCRS{S}, key::Unikey{T}, σ::Float64, ffter::FFTransformer{S}, params::Uniparams{T}) where {T, S} 78 | N = key.N 79 | b = Vector{NativePoly{T}}(undef, params.l) 80 | tbi = bufftransnativepoly(N, S) 81 | @inbounds @simd for i = 1 : params.l 82 | initialise!(tbi) 83 | mulsubto!(tbi, key.tkey[1], ta[i]) 84 | b[i] = ifft(tbi, ffter) 85 | @inbounds @simd for j = 1 : N 86 | b[i].coeffs[j] += unsigned(round(signed(T), gaussian(σ))) 87 | end 88 | end 89 | b 90 | end 91 | 92 | struct TransUniEnc{T} 93 | d::Vector{TransPoly{T}} 94 | f::TransRLEV{T} 95 | 96 | function TransUniEnc(d::Vector{<:TransPoly{T}}, f::TransRLEV{T}) where T 97 | new{T}(d, f) 98 | end 99 | end 100 | 101 | fft(ct::UniEnc, ffter::FFTransformer) = 102 | TransUniEnc(fft.(ct.d, Ref(ffter)), fft(ct.f, ffter)) 103 | 104 | ifft(ct::TransUniEnc, ffter::FFTransformer) = 105 | UniEnc(ifft.(ct.d, Ref(ffter)), ifft.(ct.f, ffter)) -------------------------------------------------------------------------------- /src/ring/polynomial.jl: -------------------------------------------------------------------------------- 1 | abstract type RingPoly{T<:Unsigned} <: AbstractVector{T} end 2 | abstract type TransPoly{T} <: AbstractVector{T} end 3 | 4 | struct NativePoly{T} <: RingPoly{T} 5 | coeffs::Vector{T} 6 | N::Int64 7 | end 8 | 9 | add(x::NativePoly, y::NativePoly) = begin 10 | @assert x.N == y.N "Two polynomials should have the same length." 11 | NativePoly(x.coeffs + y.coeffs, x.N) 12 | end 13 | 14 | add!(x::NativePoly, y::NativePoly) = begin 15 | addto!(x, x, y) 16 | end 17 | 18 | addto!(res::NativePoly, x::NativePoly, y::NativePoly) = begin 19 | @assert x.N == y.N "Two polynomials should have the same length." 20 | @assert res.N == x.N "The output and input polynomials should have the same length." 21 | @. res.coeffs = x.coeffs + y.coeffs 22 | end 23 | 24 | sub(x::NativePoly, y::NativePoly) = begin 25 | @assert x.N == y.N "Two polynomials should have the same length." 26 | NativePoly((@. x.coeffs - y.coeffs), x.N) 27 | end 28 | 29 | sub!(x::NativePoly, y::NativePoly) = begin 30 | subto!(x, x, y) 31 | end 32 | 33 | subto!(res::NativePoly, x::NativePoly, y::NativePoly) = begin 34 | @assert x.N == y.N "Two polynomials should have the same length." 35 | @assert res.N == x.N "The output and input polynomials should have the same length." 36 | @. res.coeffs = x.coeffs - y.coeffs 37 | end 38 | 39 | Base.:*(x::T, p::NativePoly{T}) where {T<:Unsigned} = 40 | NativePoly(x * p.coeffs, p.N) 41 | 42 | initialise!(p::NativePoly{T}) where {T<:Unsigned} = 43 | @. p.coeffs = zero(T) 44 | 45 | zeronativepoly(N::Int64, T::Type) = 46 | NativePoly(zeros(T, N), N) 47 | 48 | randnativepoly(N::Int64, T::Type) = 49 | NativePoly(rand(ChaCha20Stream(), T, N), N) 50 | 51 | buffnativepoly(N::Int64, T::Type) = 52 | NativePoly(Vector{T}(undef, N), N) 53 | 54 | # Changing TransNativePoly to mutable struct reduces allocations by a factor of three. 55 | struct TransNativePoly{T<:AbstractFloat} <: TransPoly{T} 56 | coeffs::Vector{Complex{T}} 57 | N::Int64 58 | end 59 | 60 | add(x::TransNativePoly, y::TransNativePoly) = begin 61 | @assert x.N == y.N "Two polynomials should have the same length." 62 | TransNativePoly((@. x.coeffs + y.coeffs), x.N) 63 | end 64 | 65 | add!(x::TransNativePoly, y::TransNativePoly) = begin 66 | addto!(x, x, y) 67 | end 68 | 69 | addto!(res::TransNativePoly, x::TransNativePoly, y::TransNativePoly) = begin 70 | @assert x.N == y.N "Two polynomials should have the same length." 71 | @assert res.N == x.N "The output and input polynomials should have the same length." 72 | @. res.coeffs = x.coeffs + y.coeffs 73 | end 74 | 75 | sub(x::TransNativePoly, y::TransNativePoly) = begin 76 | @assert x.N == y.N "Two polynomials should have the same length." 77 | TransNativePoly((@. x.coeffs - y.coeffs), x.N) 78 | end 79 | 80 | sub!(x::TransNativePoly, y::TransNativePoly) = begin 81 | subto!(x, x, y) 82 | end 83 | 84 | subto!(res::TransNativePoly, x::TransNativePoly, y::TransNativePoly) = begin 85 | @assert x.N == y.N "Two polynomials should have the same length." 86 | @assert res.N == x.N "The output and input polynomials should have the same length." 87 | @. res.coeffs = x.coeffs - y.coeffs 88 | end 89 | 90 | mul(x::TransNativePoly, y::TransNativePoly) = begin 91 | @assert x.N == y.N "Two Polynomials should have the same length." 92 | TransNativePoly((@. x.coeffs * y.coeffs), x.N) 93 | end 94 | 95 | mul!(x::TransNativePoly, y::TransNativePoly) = begin 96 | multo!(x, x, y) 97 | end 98 | 99 | multo!(res::TransNativePoly, x::TransNativePoly, y::TransNativePoly) = begin 100 | @assert x.N == y.N "Two Polynomials should have the same length." 101 | @. res.coeffs = x.coeffs * y.coeffs 102 | end 103 | 104 | # This function is the bottleneck. 105 | muladdto!(res::TransNativePoly, x::TransNativePoly, y::TransNativePoly) = begin 106 | @assert x.N == y.N "Two Polynomials should have the same length." 107 | @. res.coeffs += x.coeffs * y.coeffs 108 | end 109 | 110 | mulsubto!(res::TransNativePoly, x::TransNativePoly, y::TransNativePoly) = begin 111 | @assert x.N == y.N "Two Polynomials should have the same length." 112 | @. res.coeffs -= x.coeffs * y.coeffs 113 | end 114 | 115 | initialise!(p::TransNativePoly{T}) where {T<:AbstractFloat} = 116 | @. p.coeffs = 0 117 | 118 | zerotransnativepoly(N::Int64, T::Type) = 119 | TransNativePoly(zeros(Complex{T}, N÷2), N) 120 | 121 | bufftransnativepoly(N::Int64, T::Type) = 122 | TransNativePoly(Vector{Complex{T}}(undef, N÷2), N) -------------------------------------------------------------------------------- /src/tfhe/keygen.jl: -------------------------------------------------------------------------------- 1 | abstract type BootKey end 2 | 3 | struct BootKey_bin{T, S} <: BootKey where {T<:Unsigned, S<:AbstractFloat} 4 | brk::Vector{TransRGSW{S}} 5 | ksk::Array{LEV{T}, 3} 6 | 7 | function BootKey_bin(lwekey::LWEkey{T}, ringkey::RLWEkey{T}, kskpar::LEVparams_digit{T}, α::Float64, 8 | gswpar::GSWparams_digit{T}, β::Float64, ffter_keygen::FFTransformer{R}, ffter::FFTransformer{S}) where {T, R, S} 9 | n, k, N, D = lwekey.n, ringkey.k, ringkey.N, 1 << kskpar.logB 10 | brk = Vector{TransRGSW{S}}(undef, n) 11 | ksk = Array{LEV{T}, 3}(undef, D-1, N, k) 12 | 13 | @threads for i = 1 : n 14 | brk[i] = fft(rgsw_encrypt(lwekey.key[i], ringkey, β, ffter_keygen, gswpar), ffter) 15 | end 16 | 17 | @inbounds @simd for idx = 1 : k 18 | @threads for i = 1 : N 19 | @inbounds @simd for j = 1 : D-1 20 | ksk[j, i, idx] = lev_encrypt(T(ringkey.key[idx].coeffs[i] * j), lwekey, α, kskpar) 21 | end 22 | end 23 | end 24 | 25 | new{T, S}(brk, ksk) 26 | end 27 | end 28 | 29 | struct BootKey_block{T, S} <: BootKey where {T<:Unsigned, S<:AbstractFloat} 30 | brk::Vector{TransRGSW{S}} 31 | ksk::Array{LEV{T}, 3} 32 | 33 | function BootKey_block(lwekey::LWEkey{T}, ringkey::RLWEkey{T}, kskpar::LEVparams_digit{T}, α::Float64, 34 | gswpar::GSWparams_digit{T}, β::Float64, ffter_keygen::FFTransformer{R}, ffter::FFTransformer{S}) where {T, R, S} 35 | n, k, N, D = lwekey.n, ringkey.k, ringkey.N, 1 << kskpar.logB 36 | brk = Vector{TransRGSW{S}}(undef, n) 37 | ksk = Array{LEV{T}, 3}(undef, D÷2, N, k) 38 | 39 | @threads for i = 1 : n 40 | brk[i] = fft(rgsw_encrypt(lwekey.key[i], ringkey, β, ffter_keygen, gswpar), ffter) 41 | end 42 | 43 | @inbounds @simd for idx = 1 : k 44 | @threads for i = 1 : N 45 | if (idx-1)*N+i > n 46 | @inbounds @simd for j = 1 : D÷2 47 | ksk[j, i, idx] = lev_encrypt(T(ringkey.key[idx].coeffs[i] * j), lwekey, α, kskpar) 48 | end 49 | end 50 | end 51 | end 52 | 53 | new{T, S}(brk, ksk) 54 | end 55 | end 56 | 57 | struct BootKey_CCS{T, S} <: BootKey where {T<:Unsigned, S<:AbstractFloat} 58 | b::Vector{TransNativePoly{S}} 59 | brk::Vector{TransUniEnc{S}} 60 | ksk::Array{LEV{T}, 2} 61 | 62 | function BootKey_CCS(lwekey::LWEkey{T}, ringkey::RLWEkey{T}, kskpar::LEVparams_digit{T}, α::Float64, 63 | a::CRS{T}, unipar::Uniparams_digit{T}, β::Float64, ffter_keygen::FFTransformer{R}, ffter::FFTransformer{S}) where {T, R, S} 64 | n, N, D = lwekey.n, ringkey.N, 1 << kskpar.logB 65 | 66 | ta = fft.(a, Ref(ffter_keygen)) 67 | b = fft.(gen_b(ta, ringkey, β, ffter_keygen, unipar), Ref(ffter)) 68 | brk = Vector{TransUniEnc{S}}(undef, n) 69 | ksk = Array{LEV{T}, 2}(undef, D-1, N) 70 | 71 | @threads for i = 1 : n 72 | brk[i] = fft(unienc_encrypt(ta, lwekey.key[i], ringkey, β, ffter_keygen, unipar), ffter) 73 | end 74 | 75 | @threads for i = 1 : N 76 | @inbounds @simd for j = 1 : D-1 77 | ksk[j, i] = lev_encrypt(T(ringkey.key[1].coeffs[i] * j), lwekey, α, kskpar) 78 | end 79 | end 80 | 81 | new{T, S}(b, brk, ksk) 82 | end 83 | end 84 | 85 | struct BootKey_KMS{T, R, S} <: BootKey where {T<:Unsigned, R<:Unsigned, S<:AbstractFloat} 86 | b::Vector{TransNativePoly{S}} 87 | brk::Vector{TransRGSW{S}} 88 | rlk::TransUniEnc{S} 89 | ksk::Array{LEV{T}, 2} 90 | gswpar::GSWparams_digit{R} 91 | levpar::LEVparams_digit{R} 92 | unipar::Uniparams_digit{R} 93 | 94 | function BootKey_KMS(lwekey::LWEkey{T}, gswkey::RLWEkey{R}, unikey::RLWEkey{R}, kskpar::LEVparams_digit, α::Float64, 95 | levpar::LEVparams_digit{R}, gswpar::GSWparams_digit{R}, β::Float64, a::CRS, unipar::Uniparams_digit{R}, 96 | ffter_keygen::FFTransformer{U}, ffter::FFTransformer{S}) where {T, R, U, S} 97 | n, N, D = lwekey.n, gswkey.N, 1 << kskpar.logB 98 | 99 | ta = fft.(a, Ref(ffter_keygen)) 100 | b = fft.(gen_b(ta, unikey, β, ffter_keygen, unipar), Ref(ffter)) 101 | 102 | brk = Vector{TransRGSW{S}}(undef, n) 103 | rlk = fft(unienc_encrypt(ta, gswkey.key[1], unikey, β, ffter_keygen, unipar), ffter) 104 | ksk = Array{LEV{T}, 2}(undef, D-1, N) 105 | 106 | @threads for i = 1 : n 107 | brk[i] = fft(rgsw_encrypt(R(lwekey.key[i]), gswkey, β, ffter_keygen, gswpar), ffter) 108 | end 109 | 110 | @threads for i = 1 : N 111 | @inbounds @simd for j = 1 : D-1 112 | ksk[j, i] = lev_encrypt(T(unikey.key[1].coeffs[i] * j), lwekey, α, kskpar) 113 | end 114 | end 115 | 116 | new{T, R, S}(b, brk, rlk, ksk, gswpar, levpar, unipar) 117 | end 118 | end 119 | 120 | struct BootKey_KMS_block{T, R, S} <: BootKey where {T<:Unsigned, R<:Unsigned, S<:AbstractFloat} 121 | b::Vector{TransNativePoly{S}} 122 | ℓ::Int64 123 | d::Int64 124 | brk::Vector{TransRGSW{S}} 125 | rlk::TransUniEnc{S} 126 | ksk::Array{LEV{T}, 2} 127 | gswpar::GSWparams_digit{R} 128 | levpar::LEVparams_digit{R} 129 | unipar::Uniparams_digit{R} 130 | 131 | function BootKey_KMS_block(lwekey::LWEkey{T}, gswkey::RLWEkey{R}, unikey::RLWEkey{R}, kskpar::LEVparams_digit, α::Float64, 132 | levpar::LEVparams_digit{R}, gswpar::GSWparams_digit{R}, β::Float64, a::CRS, unipar::Uniparams_digit{R}, 133 | ffter_keygen::FFTransformer{U}, ffter::FFTransformer{S}, ℓ::Int64, d::Int64) where {T, R, U, S} 134 | n, N, D = lwekey.n, gswkey.N, 1 << kskpar.logB 135 | 136 | ta = fft.(a, Ref(ffter_keygen)) 137 | b = fft.(gen_b(ta, unikey, β, ffter_keygen, unipar), Ref(ffter)) 138 | 139 | brk = Vector{TransRGSW{S}}(undef, n) 140 | rlk = fft(unienc_encrypt(ta, gswkey.key[1], unikey, β, ffter_keygen, unipar), ffter) 141 | ksk = Array{LEV{T}, 2}(undef, D÷2, N) 142 | 143 | @threads for i = 1 : n 144 | brk[i] = fft(rgsw_encrypt(R(lwekey.key[i]), gswkey, β, ffter_keygen, gswpar), ffter) 145 | end 146 | 147 | @threads for i = n+1 : N 148 | @inbounds @simd for j = 1 : D÷2 149 | ksk[j, i] = lev_encrypt(T(unikey.key[1].coeffs[i] * j), lwekey, α, kskpar) 150 | end 151 | end 152 | 153 | new{T, R, S}(b, ℓ, d, brk, rlk, ksk, gswpar, levpar, unipar) 154 | end 155 | end -------------------------------------------------------------------------------- /src/ciphertext/lev.jl: -------------------------------------------------------------------------------- 1 | abstract type DECparams{T<:Unsigned} end 2 | abstract type LEVparams{T} <: DECparams{T} end 3 | 4 | struct LEVparams_digit{T} <: LEVparams{T} 5 | l::Int64 6 | logB::Int64 7 | halfB::T 8 | gveclog::Vector{Int64} 9 | gvec::Vector{T} 10 | mask::T 11 | 12 | function LEVparams_digit{T}(l::Int64, logB::Int64) where T 13 | B = T(1) << logB 14 | maxbits = bits(T) 15 | gveclog = maxbits .- collect(1:l) * logB 16 | gvec = T(1) .<< gveclog 17 | mask = B - 1 18 | new{T}(l, logB, B >> 1, gveclog, gvec, mask) 19 | end 20 | end 21 | 22 | mutable struct LEV{T<:Unsigned} 23 | const l::Int64 24 | stack::Vector{LWE{T}} 25 | 26 | function LEV(stack::Vector{LWE{T}}) where {T<:Unsigned} 27 | new{T}(length(stack), stack) 28 | end 29 | end 30 | 31 | function lev_encrypt(m::T, key::LEVkey{T}, σ::Float64, params::DECparams) where T 32 | stack = Vector{LWE{T}}(undef, params.l) 33 | @inbounds @simd for i = 1 : params.l 34 | stack[i] = lwe_encrypt(m * params.gvec[i], key, σ) 35 | end 36 | LEV(stack) 37 | end 38 | 39 | function lev_ith_encrypt(m::T, i::Int64, key::LEVkey{T}, σ::Float64, params::DECparams) where T 40 | stack = Vector{LWE{T}}(undef, params.l) 41 | @inbounds @simd for j = 1 : params.l 42 | stack[j] = lwe_ith_encrypt(m * params.gvec[j], i, key, σ) 43 | end 44 | LEV(stack) 45 | end 46 | 47 | add(x::LEV{T}, y::LEV{T}) where T = 48 | LEV((@. add(x.stack, y.stack))) 49 | 50 | add!(x::LEV{T}, y::LEV{T}) where T = 51 | addto!(x, x, y) 52 | 53 | addto!(res::LEV{T}, x::LEV{T}, y::LEV{T}) where T = begin 54 | @inbounds @simd for i = 1 : res.l 55 | addto!(res.stack[i], x.stack[i], y.stack[i]) 56 | end 57 | end 58 | 59 | sub(x::LEV{T}, y::LEV{T}) where T = 60 | LEV((@. sub(x.stack, y.stack))) 61 | 62 | sub!(x::LEV{T}, y::LEV{T}) where T = 63 | subto!(x, x, y) 64 | 65 | subto!(res::LEV{T}, x::LEV{T}, y::LEV{T}) where T = begin 66 | @inbounds @simd for i = 1 : res.l 67 | subto!(res.stack[i], x.stack[i], y.stack[i]) 68 | end 69 | end 70 | 71 | initialise!(x::LEV{T}) where T = begin 72 | @inbounds @simd for i = 1 : x.l 73 | initialise!(x.stack[i]) 74 | end 75 | end 76 | 77 | RLEVparams_digit{T} = LEVparams_digit{T} 78 | 79 | mutable struct RLEV{T<:Unsigned} 80 | const l::Int64 81 | stack::Vector{RLWE{T}} 82 | 83 | function RLEV(stack::Vector{RLWE{T}}) where {T<:Unsigned} 84 | new{T}(length(stack), stack) 85 | end 86 | end 87 | 88 | function rlev_encrypt(m::T, key::RLEVkey{T}, σ::Float64, ffter::FFTransformer{S}, params::DECparams) where {T<:Unsigned, S<:AbstractFloat} 89 | stack = Vector{RLWE{T}}(undef, params.l) 90 | @inbounds @simd for i = 1 : params.l 91 | stack[i] = rlwe_encrypt(params.gvec[i] * m, key, σ, ffter) 92 | end 93 | RLEV(stack) 94 | end 95 | 96 | function rlev_ith_encrypt(m::T, i::Int64, key::RLEVkey{T}, σ::Float64, ffter::FFTransformer{S}, params::DECparams) where {T<:Unsigned, S<:AbstractFloat} 97 | stack = Vector{RLWE{T}}(undef, params.l) 98 | @inbounds @simd for j = 1 : params.l 99 | stack[j] = rlwe_ith_encrypt(params.gvec[j] * m, i, key, σ, ffter) 100 | end 101 | RLEV(stack) 102 | end 103 | 104 | rlev_encrypt(m::NativePoly{T}, key::RLEVkey{T}, σ::Float64, ffter::FFTransformer{S}, params::DECparams) where {T<:Unsigned, S<:AbstractFloat} = 105 | RLEV([rlwe_encrypt(params.gvec[i] * m, key, σ, ffter) for i = 1 : params.l]) 106 | 107 | rlev_ith_encrypt(m::NativePoly{T}, i::Int64, key::RLEVkey{T}, σ::Float64, ffter::FFTransformer{S}, params::DECparams) where {T<:Unsigned, S<:AbstractFloat} = 108 | RLEV([rlwe_ith_encrypt(params.gvec[j] * m, i, key, σ, ffter) for j = 1 : params.l]) 109 | 110 | add(x::RLEV{T}, y::RLEV{T}) where T = 111 | RLEV((@. add(x.stack, y.stack))) 112 | 113 | add!(x::RLEV{T}, y::RLEV{T}) where T = 114 | addto!(x, x, y) 115 | 116 | addto!(res::RLEV{T}, x::RLEV{T}, y::RLEV{T}) where T = begin 117 | @inbounds @simd for i = 1 : res.l 118 | addto!(res.stack[i], x.stack[i], y.stack[i]) 119 | end 120 | end 121 | 122 | sub(x::RLEV{T}, y::RLEV{T}) where T = 123 | RLEV((@. sub(x.stack, y.stack))) 124 | 125 | sub!(x::RLEV{T}, y::RLEV{T}) where T = 126 | subto!(x, x, y) 127 | 128 | subto!(res::RLEV{T}, x::RLEV{T}, y::RLEV{T}) where T = begin 129 | @inbounds @simd for i = 1 : res.l 130 | subto!(res.stack[i], x.stack[i], y.stack[i]) 131 | end 132 | end 133 | 134 | initialise!(x::RLEV{T}) where T = begin 135 | @inbounds @simd for i = 1 : x.l 136 | initialise!(x.stack[i]) 137 | end 138 | end 139 | 140 | mutable struct TransRLEV{T} 141 | const l::Int64 142 | stack::Vector{TransRLWE{T}} 143 | 144 | function TransRLEV(stack::Vector{TransRLWE{T}}) where T 145 | new{T}(length(stack), stack) 146 | end 147 | end 148 | 149 | add(x::TransRLEV{T}, y::TransRLEV{T}) where T = 150 | TransRLEV((@. add(x.stack, y.stack))) 151 | 152 | add!(x::TransRLEV{T}, y::TransRLEV{T}) where T = 153 | addto!(x, x, y) 154 | 155 | addto!(res::TransRLEV{T}, x::TransRLEV{T}, y::TransRLEV{T}) where T = begin 156 | @inbounds @simd for i = 1 : res.l 157 | addto!(res.stack[i], x.stack[i], y.stack[i]) 158 | end 159 | end 160 | 161 | sub(x::TransRLEV{T}, y::TransRLEV{T}) where T = 162 | TransRLEV((@. sub(x.stack, y.stack))) 163 | 164 | sub!(x::TransRLEV{T}, y::TransRLEV{T}) where T = 165 | subto!(x, x, y) 166 | 167 | subto!(res::TransRLEV{T}, x::TransRLEV{T}, y::TransRLEV{T}) where T = begin 168 | @inbounds @simd for i = 1 : res.l 169 | subto!(res.stack[i], x.stack[i], y.stack[i]) 170 | end 171 | end 172 | 173 | mul(x::TransPoly{T}, ct::TransRLEV{T}) where T = begin 174 | TransRLEV([mul(x, ct.stack[i]) for i = eachindex(ct.stack)]) 175 | end 176 | 177 | mul!(x::TransPoly{T}, ct::TransRLEV{T}) where T = 178 | multo!(ct, x, ct) 179 | 180 | multo!(res::TransRLEV{T}, x::TransPoly{T}, ct::TransRLEV{T}) where T = begin 181 | @inbounds @simd for i = 1 : res.l 182 | multo!(res.stack[i], x, ct.stack[i]) 183 | end 184 | end 185 | 186 | initialise!(x::TransRLEV{T}) where T = begin 187 | @inbounds @simd for i = 1 : x.l 188 | initialise!(x.stack[i]) 189 | end 190 | end 191 | 192 | fft(ct::RLEV, ffter::FFTransformer) = 193 | TransRLEV((fft.(ct.stack, Ref(ffter)))) 194 | 195 | fftto!(res::TransRLEV, ct::RLEV, ffter::FFTransformer) = begin 196 | @inbounds @simd for i = 1 : res.l 197 | fftto!(res.stack[i], ct.stack[i], ffter) 198 | end 199 | end 200 | 201 | ifft(ct::TransRLEV, ffter::FFTransformer) = begin 202 | RLEV((ifft.(ct.stack, Ref(ffter)))) 203 | end 204 | 205 | ifftto!(res::RLEV, ct::TransRLEV, ffter::FFTransformer) = begin 206 | @inbounds @simd for i = 1 : res.l 207 | ifftto!(res.stack[i], ct.stack[i], ffter) 208 | end 209 | end -------------------------------------------------------------------------------- /src/ciphertext/lwe.jl: -------------------------------------------------------------------------------- 1 | mutable struct LWE{T<:Unsigned} 2 | const n::Int64 3 | b::T 4 | a::Vector{T} 5 | 6 | function LWE(b::T, a::Vector{T}) where {T<:Unsigned} 7 | new{T}(length(a), b, a) 8 | end 9 | end 10 | 11 | LWEsample(key::LWEkey{T}, σ::Float64) where T = begin 12 | e = unsigned(round(signed(T), gaussian(σ))) 13 | a = rand(ChaCha20Stream(), T, key.n) 14 | b = T(-reduce(+, a .* key.key) + e) 15 | LWE(b, a) 16 | end 17 | 18 | lwe_encrypt(m::T, key::LWEkey{T}, σ::Float64) where T = begin 19 | res = LWEsample(key, σ) 20 | res.b += m 21 | res 22 | end 23 | 24 | lwe_ith_encrypt(m::T, i::Int64, key::LWEkey{T}, σ::Float64) where T = begin 25 | res = LWEsample(key, σ) 26 | res.a[i] += m 27 | res 28 | end 29 | 30 | phase(ct::LWE{T}, key::LWEkey{T}) where T = begin 31 | reduce(+, ct.a .* key.key) + ct.b 32 | end 33 | 34 | add(x::LWE{T}, y::LWE{T}) where T = 35 | LWE(x.b + y.b, (@. x.a + y.a)) 36 | 37 | add!(x::LWE{T}, y::LWE{T}) where T = 38 | addto!(x, x, y) 39 | 40 | addto!(res::LWE{T}, x::LWE{T}, y::LWE{T}) where T = begin 41 | res.b = x.b + y.b 42 | @. res.a = x.a + y.a 43 | end 44 | 45 | sub(x::LWE{T}, y::LWE{T}) where T = 46 | LWE(x.b - y.b, (@. x.a - y.a)) 47 | 48 | sub!(x::LWE{T}, y::LWE{T}) where T = 49 | subto!(x, x, y) 50 | 51 | subto!(res::LWE{T}, x::LWE{T}, y::LWE{T}) where T = begin 52 | res.b = x.b - y.b 53 | @. res.a = x.a - y.a 54 | end 55 | 56 | initialise!(x::LWE{T}) where T = begin 57 | x.b = 0 * x.b 58 | @. x.a = 0 * x.a 59 | end 60 | 61 | mutable struct RLWE{T<:Unsigned} 62 | const k::Int64 63 | const N::Int64 64 | b::RingPoly{T} 65 | a::Vector{<:RingPoly{T}} 66 | 67 | function RLWE(b::S, a::Vector{S}) where {T<:Unsigned, S<:RingPoly{T}} 68 | @assert b.N == a[1].N 69 | new{T}(length(a), b.N, b, a) 70 | end 71 | 72 | function RLWE(b::S, a::S) where {T<:Unsigned, S<:RingPoly{T}} 73 | @assert b.N == a.N 74 | new{T}(1, b.N, b, [a]) 75 | end 76 | end 77 | 78 | function RLWEsample(key::RLWEkey{T}, σ::Float64, ffter::FFTransformer{S}) where {T, S} 79 | N = key.N 80 | a = [randnativepoly(N, T) for _ = 1 : key.k] 81 | b = buffnativepoly(N, T) 82 | tb = zerotransnativepoly(N, S) 83 | ta = zerotransnativepoly(N, S) 84 | @inbounds for i = 1 : key.k 85 | fftto!(ta, a[i], ffter) 86 | mulsubto!(tb, key.tkey[i], ta) 87 | end 88 | ifftto!(b, tb, ffter) 89 | @inbounds @simd for i = 1 : N 90 | b.coeffs[i] += unsigned(round(signed(T), gaussian(σ))) 91 | end 92 | RLWE(b, a) 93 | end 94 | 95 | rlwe_encrypt(m::T, key::RLWEkey{T}, σ::Float64, ffter::FFTransformer{S}) where {T, S} = begin 96 | res = RLWEsample(key, σ, ffter) 97 | res.b.coeffs[1] += m 98 | res 99 | end 100 | 101 | rlwe_ith_encrypt(m::T, i::Int64, key::RLWEkey{T}, σ::Float64, ffter::FFTransformer{S}) where {T, S} = begin 102 | res = RLWEsample(key, σ, ffter) 103 | res.a[i].coeffs[1] += m 104 | res 105 | end 106 | 107 | rlwe_encrypt(p::NativePoly{T}, key::RLWEkey{T}, σ::Float64, ffter::FFTransformer{S}) where {T, S} = begin 108 | res = RLWEsample(key, σ, ffter) 109 | add!(res.b, p) 110 | res 111 | end 112 | 113 | rlwe_ith_encrypt(p::NativePoly{T}, i::Int64, key::RLWEkey{T}, σ::Float64, ffter::FFTransformer{S}) where {T, S} = begin 114 | res = RLWEsample(key, σ, ffter) 115 | add!(res.a[i], p) 116 | res 117 | end 118 | 119 | function phase(ct::RLWE{T}, key::RLWEkey{T}, ffter::FFTransformer{S}) where {T, S} 120 | N = ct.N 121 | tres = zerotransnativepoly(N, S) 122 | ta = zerotransnativepoly(N, S) 123 | tkey = [TransNativePoly(Complex{S}.(key.tkey[i].coeffs), N) for i = 1 : key.k] 124 | fftto!(tres, ct.b, ffter) 125 | @inbounds for i = 1 : key.k 126 | fftto!(ta, ct.a[i], ffter) 127 | muladdto!(tres, tkey[i], ta) 128 | end 129 | ifft(tres, ffter) 130 | end 131 | 132 | add(x::RLWE{T}, y::RLWE{T}) where T = 133 | RLWE(add(x.b, y.b), (@. add(x.a, y.a))) 134 | 135 | add!(x::RLWE{T}, y::RLWE{T}) where T = 136 | addto!(x, x, y) 137 | 138 | addto!(res::RLWE{T}, x::RLWE{T}, y::RLWE{T}) where T = begin 139 | addto!(res.b, x.b, y.b) 140 | @inbounds @simd for i = 1 : res.k 141 | addto!(res.a[i], x.a[i], y.a[i]) 142 | end 143 | end 144 | 145 | sub(x::RLWE{T}, y::RLWE{T}) where T = 146 | RLWE(sub(x.b, y.b), (@. sub(x.a, y.a))) 147 | 148 | sub!(x::RLWE{T}, y::RLWE{T}) where T = 149 | subto!(x, x, y) 150 | 151 | subto!(res::RLWE{T}, x::RLWE{T}, y::RLWE{T}) where T = begin 152 | subto!(res.b, x.b, y.b) 153 | @inbounds @simd for i = 1 : res.k 154 | subto!(res.a[i], x.a[i], y.a[i]) 155 | end 156 | end 157 | 158 | initialise!(x::RLWE{T}) where T = begin 159 | initialise!(x.b) 160 | @inbounds @simd for i = 1 : x.k 161 | initialise!(x.a[i]) 162 | end 163 | end 164 | 165 | mutable struct TransRLWE{T} 166 | const k::Int64 167 | b::TransPoly{T} 168 | a::Vector{TransPoly{T}} 169 | 170 | function TransRLWE(b::S, a::Vector{S}) where {T, S<:TransPoly{T}} 171 | @assert a[1].N == b.N 172 | new{T}(length(a), b, a) 173 | end 174 | 175 | function TransRLWE(b::S, a::S) where {T, S<:TransPoly{T}} 176 | @assert a.N == b.N 177 | new{T}(1, b, [a]) 178 | end 179 | end 180 | 181 | add(x::TransRLWE{T}, y::TransRLWE{T}) where T = 182 | TransRLWE(add(x.b, y.b), (@. add(x.a, y.a))) 183 | 184 | add!(x::TransRLWE{T}, y::TransRLWE{T}) where T = 185 | addto!(x, x, y) 186 | 187 | addto!(res::TransRLWE{T}, x::TransRLWE{T}, y::TransRLWE{T}) where T = begin 188 | addto!(res.b, x.b, y.b) 189 | @inbounds @simd for i = 1 : res.k 190 | addto!(res.a[i], x.a[i], y.a[i]) 191 | end 192 | end 193 | 194 | sub(x::TransRLWE{T}, y::TransRLWE{T}) where T = 195 | TransRLWE(sub(x.b, y.b), (@. sub(x.a, y.a))) 196 | 197 | sub!(x::TransRLWE{T}, y::TransRLWE{T}) where T = 198 | subto!(x, x, y) 199 | 200 | subto!(res::TransRLWE{T}, x::TransRLWE{T}, y::TransRLWE{T}) where T = begin 201 | subto!(res.b, x.b, y.b) 202 | @inbounds @simd for i = 1 : res.k 203 | subto!(res.a[i], x.a[i], y.a[i]) 204 | end 205 | end 206 | 207 | mul(x::TransPoly{T}, ct::TransRLWE{T}) where T = begin 208 | TransRLWE(mul(x, ct.b), [mul(x, ct.a[i]) for i = 1 : ct.k]) 209 | end 210 | 211 | mul!(x::TransPoly{T}, ct::TransRLWE{T}) where T = 212 | multo!(ct, x, ct) 213 | 214 | multo!(res::TransRLWE{T}, x::TransPoly{T}, ct::TransRLWE{T}) where T = begin 215 | multo!(res.b, x, ct.b) 216 | @inbounds @simd for i = 1 : res.k 217 | multo!(res.a[i], x, ct.a[i]) 218 | end 219 | end 220 | 221 | muladdto!(res::TransRLWE{T}, x::TransPoly{T}, ct::TransRLWE{T}) where T = begin 222 | muladdto!(res.b, x, ct.b) 223 | @inbounds @simd for i = 1 : res.k 224 | muladdto!(res.a[i], x, ct.a[i]) 225 | end 226 | end 227 | 228 | mulsubto!(res::TransRLWE{T}, x::TransPoly{T}, ct::TransRLWE{T}) where T = begin 229 | mulsubto!(res.b, x, ct.b) 230 | @inbounds @simd for i = 1 : res.k 231 | mulsubto!(res.a[i], x, ct.a[i]) 232 | end 233 | end 234 | 235 | initialise!(x::TransRLWE{T}) where T = begin 236 | initialise!(x.b) 237 | @inbounds @simd for i = 1 : x.k 238 | initialise!(x.a[i]) 239 | end 240 | end 241 | 242 | fft(ct::RLWE, ffter::FFTransformer) = 243 | TransRLWE(fft(ct.b, ffter), (fft.(ct.a, Ref(ffter)))) 244 | 245 | fftto!(res::TransRLWE, ct::RLWE, ffter::FFTransformer) = begin 246 | fftto!(res.b, ct.b, ffter) 247 | @inbounds @simd for i = 1 : res.k 248 | fftto!(res.a[i], ct.a[i], ffter) 249 | end 250 | end 251 | 252 | ifft(ct::TransRLWE, ffter::FFTransformer) = 253 | RLWE(ifft(ct.b, ffter), (ifft.(ct.a, Ref(ffter)))) 254 | 255 | ifftto!(res::RLWE, ct::TransRLWE, ffter::FFTransformer) = begin 256 | ifftto!(res.b, ct.b, ffter) 257 | @inbounds @simd for i = 1 : res.k 258 | ifftto!(res.a[i], ct.a[i], ffter) 259 | end 260 | end -------------------------------------------------------------------------------- /src/ring/fft.jl: -------------------------------------------------------------------------------- 1 | function bit_reverse!(μ::Vector{T}) where T 2 | j = 0 3 | n = length(μ) 4 | for i = 1:n-1 5 | bit = n >> 1 6 | while j ≥ bit 7 | j -= bit 8 | bit >>= 1 9 | end 10 | j += bit 11 | if i < j 12 | μ[i+1], μ[j+1] = μ[j+1], μ[i+1] 13 | end 14 | end 15 | end 16 | 17 | # For reasons unknown, making FFTransformer mutable and setting every value it holds constant reduces a lot of allocations. 18 | mutable struct FFTransformer{T} 19 | const N::Int64 20 | const Ψ::Vector{Complex{T}} 21 | const Ψinv::Vector{Complex{T}} 22 | const roots::Vector{Complex{T}} 23 | const rootsinv::Vector{Complex{T}} 24 | const mask::Union{UInt32, UInt64} 25 | 26 | function FFTransformer{T}(N::Int64, bits::Int64) where {T<:AbstractFloat} 27 | @assert bits == 32 || bits == 64 28 | 29 | mask = bits == 32 ? 0xffffffff : 0xffffffffffffffff 30 | 31 | halfN = N >> 1 32 | idx = collect(0 : halfN-1) 33 | Ψ = Complex{T}.(exp.((-im * big(π) / halfN) .* idx)) 34 | Ψinv = Complex{T}.(exp.((im * big(π) / halfN) .* idx)) 35 | 36 | bit_reverse!(Ψ) 37 | bit_reverse!(Ψinv) 38 | 39 | # We scale rootsinv instead of dividing by N, for simplicity. 40 | roots = Complex{T}.(exp.((im * big(π) / N) .* idx)) 41 | rootinv = Complex{T}.(exp.((-im * big(π) / N) .* idx) / halfN) 42 | 43 | new(N, Ψ, Ψinv, roots, rootinv, mask) 44 | end 45 | end 46 | 47 | # Twisting Z[X]/(Xᴺ+1) to Z(i)[X]/(X^(N/2)+1). 48 | function fft(p::NativePoly, ffter::FFTransformer{T}) where {T<:AbstractFloat} 49 | N = p.N; halfN = N >> 1 50 | a = (signed.(p.coeffs[1:halfN]) - 1im * signed.(p.coeffs[halfN+1:end])) .* ffter.roots 51 | fft!(a, ffter.Ψ) 52 | 53 | TransNativePoly(a, N) 54 | end 55 | 56 | # Twisting Z[X]/(Xᴺ+1) to Z(i)[X]/(X^(N/2)+1). 57 | function fftto!(t::TransNativePoly, p::NativePoly, ffter::FFTransformer{T}) where {T<:AbstractFloat} 58 | halfN = p.N >> 1 59 | @inbounds @simd for i = 1 : halfN 60 | t.coeffs[i] = (signed(p.coeffs[i]) - im * signed(p.coeffs[halfN + i])) * ffter.roots[i] 61 | end 62 | fft!(t.coeffs, ffter.Ψ) 63 | end 64 | 65 | # Twisting Z[X]/(Xᴺ+1) to Z(i)[X]/(X^(N/2)+1). 66 | function ifft(p::TransNativePoly, ffter::FFTransformer{T}) where {T<:AbstractFloat} 67 | ifft!(p.coeffs, ffter.Ψinv) 68 | @. p.coeffs *= ffter.rootsinv 69 | 70 | NativePoly(native.(vcat(real(p.coeffs), -imag(p.coeffs)), [ffter.mask]), p.N) 71 | end 72 | 73 | # Twisting Z[X]/(Xᴺ+1) to Z(i)[X]/(X^(N/2)+1). 74 | function ifftto!(p::NativePoly, t::TransNativePoly, ffter::FFTransformer{T}) where {T<:AbstractFloat} 75 | N = p.N; halfN = N >> 1 76 | ifft!(t.coeffs, ffter.Ψinv) 77 | @. t.coeffs *= ffter.rootsinv 78 | 79 | @. p.coeffs[1:halfN] = native(real(t.coeffs), ffter.mask) 80 | @. p.coeffs[halfN+1:end] = native(-imag(t.coeffs), ffter.mask) 81 | end 82 | 83 | # Gentleman-Sande over C[X]/(Xᴺ+1). 84 | # Based on https://eprint.iacr.org/2016/504 85 | function ifft!(a::Vector{T}, Ψinv::Vector{T}) where {T<:Complex{<:AbstractFloat}} 86 | N = length(a) 87 | 88 | m = N >> 1 89 | logkp1 = 1 90 | k = 1 91 | while m > 0 92 | @inbounds @simd for i = 0 : m - 1 93 | j1 = i << logkp1 + 1; j2 = j1 + k - 1 94 | @inbounds @simd for j = j1 : j2 95 | t, u = a[j], a[j+k] 96 | a[j], a[j+k] = t + u, Ψinv[m+i+1] * (t - u) 97 | end 98 | end 99 | m >>= 1; logkp1 += 1; k <<= 1 100 | end 101 | end 102 | 103 | # Cooley-Tukey. 104 | # Based on https://eprint.iacr.org/2016/504 105 | function fft!(a::Vector{T}, Ψ::Vector{T}) where {T<:Complex{<:AbstractFloat}} 106 | N = length(a) 107 | 108 | m, logkp1, k = 1, trailing_zeros(N), N >> 1 109 | 110 | @inbounds @simd for j = 1 : k 111 | t, u = a[j], a[j+k] * Ψ[2] 112 | a[j], a[j+k] = t + u, t - u 113 | end 114 | m <<= 1; logkp1 -= 1; k >>= 1 115 | 116 | while logkp1 > 3 117 | @inbounds @simd for i = 0 : m-1 118 | j1 = i << logkp1 + 1; j2 = j1 + k - 1 119 | @inbounds @simd for j = j1 : 8 : j2 120 | t1, t2, t3, t4, t5, t6, t7, t8 = a[j], a[j+1], a[j+2], a[j+3], a[j+4], a[j+5], a[j+6], a[j+7] 121 | u1, u2, u3, u4 = a[j+k] * Ψ[m+i+1], a[j+k+1] * Ψ[m+i+1], a[j+k+2] * Ψ[m+i+1], a[j+k+3] * Ψ[m+i+1] 122 | u5, u6, u7, u8 = a[j+k+4] * Ψ[m+i+1], a[j+k+5] * Ψ[m+i+1], a[j+k+6] * Ψ[m+i+1], a[j+k+7] * Ψ[m+i+1] 123 | a[j], a[j+1], a[j+2], a[j+3], a[j+4], a[j+5], a[j+6], a[j+7] = t1 + u1, t2 + u2, t3 + u3, t4 + u4, t5 + u5, t6 + u6, t7 + u7, t8 + u8 124 | a[j+k], a[j+k+1], a[j+k+2], a[j+k+3], a[j+k+4], a[j+k+5], a[j+k+6], a[j+k+7] = t1 - u1, t2 - u2, t3 - u3, t4 - u4, t5 - u5, t6 - u6, t7 - u7, t8 - u8 125 | end 126 | end 127 | m <<= 1; logkp1 -= 1; k >>= 1 128 | end 129 | 130 | if logkp1 == 3 131 | @inbounds @simd for i = 0 : m-1 132 | j = i << 3 + 1 133 | t1, t2, t3, t4, u1, u2, u3, u4 = a[j], a[j+1], a[j+2], a[j+3], a[j+k] * Ψ[m+i+1], a[j+k+1] * Ψ[m+i+1], a[j+k+2] * Ψ[m+i+1], a[j+k+3] * Ψ[m+i+1] 134 | a[j], a[j+1], a[j+2], a[j+3], a[j+k], a[j+k+1], a[j+k+2], a[j+k+3] = t1 + u1, t2 + u2, t3 + u3, t4 + u4, t1 - u1, t2 - u2, t3 - u3, t4 - u4 135 | end 136 | m <<= 1; logkp1 -= 1; k >>= 1 137 | end 138 | 139 | if logkp1 == 2 140 | @inbounds @simd for i = 0 : m-1 141 | j = i << 2 + 1 142 | t1, t2, u1, u2 = a[j], a[j+1], a[j+k] * Ψ[m+i+1], a[j+k+1] * Ψ[m+i+1] 143 | a[j], a[j+1], a[j+k], a[j+k+1] = t1 + u1, t2 + u2, t1 - u1, t2 - u2 144 | end 145 | m <<= 1; logkp1 -= 1; k >>= 1 146 | end 147 | 148 | if logkp1 == 1 149 | @inbounds @simd for i = 0 : m-1 150 | j = i << 1 + 1 151 | t, u = a[j], a[j+k] * Ψ[m+i+1] 152 | a[j], a[j+k] = t + u, t - u 153 | end 154 | end 155 | end 156 | 157 | # Gentleman-Sande. 158 | # Based on https://eprint.iacr.org/2016/504 159 | function ifft!(a::Vector{T}, Ψinv::Vector{T}) where {T<:Complex{<:AbstractFloat}} 160 | N = length(a) 161 | 162 | m, logkp1, k = N >> 1, 1, 1 163 | 164 | @inbounds @simd for i = 0 : m - 1 165 | j = i << 1 + 1 166 | t, u = a[j], a[j+k] 167 | a[j], a[j+k] = t + u, (t - u) * Ψinv[m+i+1] 168 | end 169 | m >>= 1; logkp1 += 1; k <<= 1 170 | 171 | if m > 0 172 | @inbounds @simd for i = 0 : m - 1 173 | j = i << 2 + 1 174 | t1, t2, u1, u2 = a[j], a[j+1], a[j+k], a[j+k+1] 175 | a[j], a[j+1], a[j+k], a[j+k+1] = t1 + u1, t2 + u2, (t1-u1) * Ψinv[m+i+1], (t2-u2) * Ψinv[m+i+1] 176 | end 177 | m >>= 1; logkp1 += 1; k <<= 1 178 | end 179 | 180 | if m > 0 181 | @inbounds @simd for i = 0 : m - 1 182 | j = i << 3 + 1 183 | t1, t2, t3, t4, u1, u2, u3, u4 = a[j], a[j+1], a[j+2], a[j+3], a[j+k], a[j+k+1], a[j+k+2], a[j+k+3] 184 | a[j], a[j+1], a[j+2], a[j+3], a[j+k], a[j+k+1], a[j+k+2], a[j+k+3] = t1 + u1, t2 + u2, t3 + u3, t4 + u4, (t1-u1) * Ψinv[m+i+1], (t2-u2) * Ψinv[m+i+1], (t3-u3) * Ψinv[m+i+1], (t4-u4) * Ψinv[m+i+1] 185 | end 186 | m >>= 1; logkp1 += 1; k <<= 1 187 | end 188 | 189 | while m > 1 190 | @inbounds @simd for i = 0 : m - 1 191 | j1 = i << logkp1 + 1; j2 = j1 + k - 1 192 | @inbounds @simd for j = j1 : 8 : j2 193 | t1, t2, t3, t4, t5, t6, t7, t8 = a[j], a[j+1], a[j+2], a[j+3], a[j+4], a[j+5], a[j+6], a[j+7] 194 | u1, u2, u3, u4, u5, u6, u7, u8 = a[j+k], a[j+k+1], a[j+k+2], a[j+k+3], a[j+k+4], a[j+k+5], a[j+k+6], a[j+k+7] 195 | 196 | a[j], a[j+1], a[j+2], a[j+3], a[j+4], a[j+5], a[j+6], a[j+7] = t1 + u1, t2 + u2, t3 + u3, t4 + u4, t5 + u5, t6 + u6, t7 + u7, t8 + u8 197 | a[j+k], a[j+k+1], a[j+k+2], a[j+k+3] = (t1-u1) * Ψinv[m+i+1], (t2-u2) * Ψinv[m+i+1], (t3-u3) * Ψinv[m+i+1], (t4-u4) * Ψinv[m+i+1] 198 | a[j+k+4], a[j+k+5], a[j+k+6], a[j+k+7] = (t5-u5) * Ψinv[m+i+1], (t6-u6) * Ψinv[m+i+1], (t7-u7) * Ψinv[m+i+1], (t8-u8) * Ψinv[m+i+1] 199 | end 200 | end 201 | m >>= 1; logkp1 += 1; k <<= 1 202 | end 203 | 204 | if m > 0 205 | @inbounds @simd for j = 1 : k 206 | t, u = a[j], a[j+k] 207 | a[j], a[j+k] = t + u, (t - u ) * Ψinv[2] 208 | end 209 | end 210 | end -------------------------------------------------------------------------------- /src/ciphertext/gsw.jl: -------------------------------------------------------------------------------- 1 | abstract type GSWparams{T} <: DECparams{T} end 2 | 3 | struct GSWparams_digit{T} <: GSWparams{T} 4 | k::Int64 5 | l::Int64 6 | logB::Int64 7 | halfB::T 8 | gveclog::Vector{Int64} 9 | gvec::Vector{T} 10 | mask::T 11 | 12 | function GSWparams_digit{T}(k::Int64, l::Int64, logB::Int64) where T 13 | B = T(1) << logB 14 | maxbits = bits(T) 15 | gveclog = maxbits .- collect(1:l) * logB 16 | gvec = T(1) .<< gveclog 17 | mask = B - 1 18 | new{T}(k, l, logB, B >> 1, gveclog, gvec, mask) 19 | end 20 | end 21 | 22 | @inline decomp(a::T, params::LEVparams_digit{T}) where T = begin 23 | res = Vector{T}(undef, params.l) 24 | decompto!(res, a, params) 25 | res 26 | end 27 | 28 | @inline decomp(a::Vector{T}, params::GSWparams_digit{T}) where T = begin 29 | res = Array{T, 2}(undef, params.l, params.k) 30 | decompto!(res, a, params) 31 | res 32 | end 33 | 34 | @inline unbalanceddecompto!(avec::Vector{T}, a::T, params::Union{LEVparams_digit{T}, GSWparams_digit{T}}) where T = begin 35 | ai = divbits(a, params.gveclog[end]) 36 | @inbounds for i = params.l : -1 : 1 37 | avec[i] = ai & params.mask 38 | ai >>= params.logB 39 | end 40 | end 41 | 42 | @inline decompto!(avec::Vector{T}, a::T, params::Union{LEVparams_digit{T}, GSWparams_digit{T}}) where T = begin 43 | ai = divbits(a, params.gveclog[end]) 44 | @inbounds for i = params.l : -1 : 2 45 | avec[i] = ai & params.mask 46 | ai >>= params.logB 47 | ai += avec[i] >> (params.logB - 1) 48 | avec[i] -= (avec[i] & params.halfB) << 1 49 | end 50 | avec[1] = ai & params.mask 51 | avec[1] -= (avec[1] & params.halfB) << 1 52 | end 53 | 54 | @inline decompto!(avec::Array{T, 2}, a::Vector{T}, params::GSWparams_digit{T}) where T = begin 55 | ai = @. divbits(a, params.gveclog[end]) 56 | @inbounds for i = params.l : -1 : 2 57 | @. avec[i, :] = ai & params.mask 58 | @. ai >>= params.logB 59 | @. ai += (avec[i, :] >> (params.logB - 1)) 60 | @. avec[i, :] -= (avec[i, :] & params.halfB) << 1 61 | end 62 | @. avec[1, :] = ai & params.mask 63 | @. avec[1, :] -= (ai & params.halfB) << 1 64 | end 65 | 66 | @inline decomp(a::NativePoly{T}, params::Union{LEVparams_digit{T}, GSWparams_digit{T}}) where T = begin 67 | res = Vector{NativePoly{T}}(undef, params.l) 68 | @inbounds @simd for j = 1 : params.l 69 | res[j] = buffnativepoly(a.N, T) 70 | end 71 | decompto!(res, a, params) 72 | res 73 | end 74 | 75 | @inline decomp(a::Vector{NativePoly{T}}, params::GSWparams_digit{T}) where T = begin 76 | res = Array{NativePoly{T}}(undef, params.l, params.k) 77 | @inbounds @simd for i = 1 : params.k 78 | @inbounds @simd for j = 1 : params.l 79 | res[j, i] = buffnativepoly(a.N, T) 80 | end 81 | end 82 | decompto!(res, a, params) 83 | res 84 | end 85 | 86 | @inline decompto!(avec::Vector{NativePoly{T}}, a::NativePoly{T}, params::Union{LEVparams_digit{T}, GSWparams_digit{T}}) where T = begin 87 | @. avec[1].coeffs = divbits(a.coeffs, params.gveclog[end]) 88 | @inbounds for j = params.l : -1 : 2 89 | @. avec[j].coeffs = avec[1].coeffs & params.mask 90 | @. avec[1].coeffs >>= params.logB 91 | @. avec[1].coeffs += avec[j].coeffs >> (params.logB - 1) 92 | @. avec[j].coeffs -= (avec[j].coeffs & params.halfB) << 1 93 | end 94 | @. avec[1].coeffs &= params.mask 95 | @. avec[1].coeffs -= (avec[1].coeffs & params.halfB) << 1 96 | end 97 | 98 | @inline decompto!(avec::Array{NativePoly{T}, 2}, a::Vector{NativePoly{T}}, params::GSWparams_digit{T}) where T = begin 99 | @inbounds @simd for idx = 1 : params.k 100 | @. avec[1, idx].coeffs = divbits(a[idx].coeffs, params.gveclog[end]) 101 | @inbounds for j = params.l : -1 : 2 102 | @. avec[j, idx].coeffs = avec[1, idx].coeffs & params.mask 103 | @. avec[1, idx].coeffs >>= params.logB 104 | @. avec[1, idx].coeffs += avec[j, idx].coeffs >> (params.logB - 1) 105 | @. avec[j, idx].coeffs -= (avec[j, idx].coeffs & params.halfB) << 1 106 | end 107 | @. avec[1, idx].coeffs &= params.mask 108 | @. avec[1, idx].coeffs -= (avec[1, idx].coeffs & params.halfB) << 1 109 | end 110 | end 111 | 112 | mutable struct GSW{T<:Unsigned} 113 | const k::Int64 114 | basketb::LEV{T} 115 | basketa::Vector{LEV{T}} 116 | 117 | function GSW(basketb::LEV{T}, basketa::Vector{LEV{T}}) where {T<:Unsigned} 118 | new{T}(length(basketa), basketb, basketa) 119 | end 120 | end 121 | 122 | function gsw_encrypt(m::T, key::GSWkey{T}, σ::Float64, params::GSWparams) where T 123 | basketb = lev_encrypt(m, key, σ, params) 124 | basketa = Vector{LEV{T}}(undef, params.k) 125 | @inbounds @simd for i = 1 : params.k 126 | basketa[i] = lev_ith_encrypt(m, i, key, σ, params) 127 | end 128 | GSW(basketb, basketa) 129 | end 130 | 131 | add(x::GSW{T}, y::GSW{T}) where T = 132 | GSW(add(x.basketb, y.basketb), (@. add(x.basket, y.basket))) 133 | 134 | add!(x::GSW{T}, y::GSW{T}) where {T<:Unsigned} = 135 | addto!(x, x, y) 136 | 137 | addto!(res::GSW{T}, x::GSW{T}, y::GSW{T}) where T = begin 138 | addto!(res.basketb, x.basketb, y.basketb) 139 | @inbounds @simd for i = eachindex(x.basket) 140 | addto!(res.basketa[i], x.basketa[i], y.basketa[i]) 141 | end 142 | end 143 | 144 | sub(x::GSW{T}, y::GSW{T}) where T = 145 | GSW(sub(x.basketb, y.basketb), (@. sub(x.basketa, y.basketa))) 146 | 147 | sub!(x::GSW{T}, y::GSW{T}) where T = 148 | subto!(x, x, y) 149 | 150 | subto!(res::GSW{T}, x::GSW{T}, y::GSW{T}) where T = begin 151 | subto!(res.basketb, x.basketb, y.basketb) 152 | @inbounds @simd for i = eachindex(x.basket) 153 | subto!(res.basketa[i], x.basketa[i], y.basketa[i]) 154 | end 155 | end 156 | 157 | initialise!(x::GSW{T}) where T = begin 158 | initialise!(x.basketb) 159 | @inbounds @simd for i = 1 : x.k 160 | initialise!(x.basketa[i]) 161 | end 162 | end 163 | 164 | mutable struct RGSW{T<:Unsigned} 165 | const k::Int64 166 | basketb::RLEV{T} 167 | basketa::Vector{RLEV{T}} 168 | 169 | function RGSW(basketb::RLEV{T}, basketa::Vector{RLEV{T}}) where {T<:Unsigned} 170 | new{T}(length(basketa), basketb, basketa) 171 | end 172 | end 173 | 174 | function rgsw_encrypt(m::T, key::RGSWkey{T}, σ::Float64, ffter::FFTransformer{S}, params::GSWparams) where {T, S} 175 | basketb = rlev_encrypt(m, key, σ, ffter, params) 176 | basketa = [rlev_ith_encrypt(m, i, key, σ, ffter, params) for i = 1 : params.k] 177 | RGSW(basketb, basketa) 178 | end 179 | 180 | function rgsw_encrypt(p::NativePoly{T}, key::RGSWkey{T}, σ::Float64, ffter::FFTransformer{S}, params::GSWparams) where {T, S} 181 | basketb = rlev_encrypt(p, key, σ, ffter, params) 182 | basketa = [rlev_ith_encrypt(p, i, key, σ, ffter, params) for i = 1 : params.k] 183 | RGSW(basketb, basketa) 184 | end 185 | 186 | add(x::RGSW{T}, y::RGSW{T}) where T = 187 | RGSW(add(x.basketb, y.basketb), (@. add(x.basketa, y.basketa))) 188 | 189 | add!(x::RGSW{T}, y::RGSW{T}) where T = 190 | addto!(x, x, y) 191 | 192 | addto!(res::RGSW{T}, x::RGSW{T}, y::RGSW{T}) where T = begin 193 | addto!(res.basketb, x.basketb, y.basketb) 194 | @inbounds @simd for i = eachindex(x.basket) 195 | addto!(res.basketa[i], x.basketa[i], y.basketa[i]) 196 | end 197 | end 198 | 199 | sub(x::RGSW{T}, y::RGSW{T}) where T = 200 | RGSW(sub(x.basketb, y.basketb), (@. sub(x.basketa, y.basketa))) 201 | 202 | sub!(x::RGSW{T}, y::RGSW{T}) where T = 203 | subto!(x, x, y) 204 | 205 | subto!(res::RGSW{T}, x::RGSW{T}, y::RGSW{T}) where T = begin 206 | subto!(res.basketb, x.basketb, y.basketb) 207 | @inbounds @simd for i = eachindex(x.basket) 208 | subto!(res.basketa[i], x.basketa[i], y.basketa[i]) 209 | end 210 | end 211 | 212 | initialise!(x::RGSW{T}) where T = begin 213 | initialise!(x.basketb) 214 | @inbounds @simd for i = 1 : x.k 215 | initialise!(x.basketa[i]) 216 | end 217 | end 218 | 219 | mutable struct TransRGSW{T} 220 | const k::Int64 221 | basketb::TransRLEV{T} 222 | basketa::Vector{TransRLEV{T}} 223 | 224 | function TransRGSW(basketb::TransRLEV{T}, basketa::Vector{TransRLEV{T}}) where T 225 | new{T}(length(basketa), basketb, basketa) 226 | end 227 | end 228 | 229 | add(x::TransRGSW{T}, y::TransRGSW{T}) where T = 230 | TransRGSW(add(x.basketb, y.basketb), (@. add(x.basketa, y.basketa))) 231 | 232 | add!(x::TransRGSW{T}, y::TransRGSW{T}) where T = 233 | addto!(x, x, y) 234 | 235 | addto!(res::TransRGSW{T}, x::TransRGSW{T}, y::TransRGSW{T}) where T = begin 236 | addto!(res.basketb, x.basketb, y.basketb) 237 | @inbounds @simd for i = eachindex(x.basket) 238 | addto!(res.basketa[i], x.basketa[i], y.basketa[i]) 239 | end 240 | end 241 | 242 | sub(x::TransRGSW{T}, y::TransRGSW{T}) where T = 243 | TransRGSW(sub(x.basketb, y.basketb), (@. sub(x.basketa, y.basketa))) 244 | 245 | sub!(x::TransRGSW{T}, y::TransRGSW{T}) where T = 246 | subto!(x, x, y) 247 | 248 | subto!(res::TransRGSW{T}, x::TransRGSW{T}, y::TransRGSW{T}) where T = begin 249 | subto!(res.basketb, x.basketb, y.basketb) 250 | @inbounds @simd for i = eachindex(x.basket) 251 | subto!(res.basketa[i], x.basketa[i], y.basketa[i]) 252 | end 253 | end 254 | 255 | initialise!(x::TransRGSW{T}) where T = begin 256 | initialise!(x.basketb) 257 | @inbounds @simd for i = 1 : x.k 258 | initialise!(x.basketa[i]) 259 | end 260 | end 261 | 262 | fft(ct::RGSW, ffter::FFTransformer) = 263 | TransRGSW(fft(ct.basketb, ffter), (fft.(ct.basketa, Ref(ffter)))) 264 | 265 | fftto!(res::TransRGSW, ct::RGSW, ffter::FFTransformer) = begin 266 | fftto!(res.basketb, ct.basketb, ffter) 267 | @inbounds @simd for i = 1 : res.kp1 268 | fftto!(res.basketa[i], ct.basketa[i], ffter) 269 | end 270 | end 271 | 272 | ifft(ct::TransRGSW, ffter::FFTransformer) = 273 | RGSW(ifft(ct.basketb, ffter), (ifft.(ct.basketa, Ref(ffter)))) 274 | 275 | ifftto!(res::RGSW, ct::TransRGSW, ffter::FFTransformer) = begin 276 | ifftto!(res.basketb, ct.basketb, ffter) 277 | @inbounds @simd for i = 1 : res.kp1 278 | ifftto!(res.basketa[i], ct.basketa[i], ffter) 279 | end 280 | end -------------------------------------------------------------------------------- /src/tfhe/scheme.jl: -------------------------------------------------------------------------------- 1 | # T is unsigned integer for (R)LWE, R, S are Float type for FFT operations in KeyGen and BlindRotate, respectively. 2 | abstract type TFHEparams{T<:Unsigned, R<:AbstractFloat, S<:AbstractFloat} end 3 | 4 | abstract type CGGIparams{T, R, S} <: TFHEparams{T, R, S} end 5 | 6 | struct TFHEparams_bin{T, R, S} <: CGGIparams{T, R, S} 7 | n::Int64 # LWE dimension 8 | α::Float64 # LWE noise standard deviation 9 | 10 | f::Int64 # key-switching gadget length 11 | logD::Int64 # key-switching gadget size 12 | 13 | N::Int64 # RLWE dimension 14 | k::Int64 # RLWE length 15 | β::Float64 # RLWE noise standard deviation 16 | 17 | l_gsw::Int64 # blind-rotation gadget length 18 | logB_gsw::Int64 # blind-rotation gadget size 19 | end 20 | 21 | # LMSS23 : Faster TFHE Bootstrapping from Block Binary Distribution 22 | struct TFHEparams_block{T, R, S} <: CGGIparams{T, R, S} 23 | d::Int64 # number of the blocks 24 | ℓ::Int64 # length of each block 25 | α::Float64 # LWE noise standard deviation 26 | 27 | f::Int64 # key-switching gadget length 28 | logD::Int64 # key-switching gadget size 29 | 30 | N::Int64 # RLWE dimension 31 | k::Int64 # RLWE length 32 | β::Float64 # RLWE noise standard deviation 33 | 34 | l_gsw::Int64 # blind-rotation gadget length 35 | logB_gsw::Int64 # blind-rotation gadget size 36 | end 37 | 38 | abstract type MKTFHEparams{T, R, S} <: TFHEparams{T, R, S} end 39 | 40 | struct CCSparams{T, R, S} <: MKTFHEparams{T, R, S} 41 | n::Int64 # LWE dimension 42 | α::Float64 # LWE noise standard deviation 43 | 44 | f::Int64 # key-switching gadget length 45 | logD::Int64 # key-switching gadget size 46 | 47 | N::Int64 # RLWE dimension 48 | β::Float64 # RLWE noise standard deviation 49 | 50 | l_uni::Int64 # blind-rotation gadget length 51 | logB_uni::Int64 # blind-rotation gadget size 52 | 53 | k::Int64 # number of the parties 54 | end 55 | 56 | # T is unsigned for LWE, R is unsigned for RLWE and U, S are Float for FFT opertaions in KeyGen and BlindRotate, respectively. 57 | struct KMSparams{T, R, U, S} <: MKTFHEparams{R, U, S} where {T<:Unsigned} 58 | n::Int64 # LWE dimension 59 | α::Float64 # LWE noise standard deviation 60 | 61 | f::Int64 # key-switching gadget length 62 | logD::Int64 # key-switching gadget size 63 | 64 | N::Int64 # RLWE dimension 65 | β::Float64 # RLWE noise standard deviation 66 | 67 | l_gsw::Int64 # GSW gadget length 68 | logB_gsw::Int64 # GSW gadget size 69 | 70 | l_lev::Int64 # LEV gadget length 71 | logB_lev::Int64 # LEV gadget size 72 | 73 | l_uni::Int64 # UniEnc gadget length 74 | logB_uni::Int64 # UniEnc gadget size 75 | 76 | k::Int64 # number of the parties 77 | end 78 | 79 | # T is unsigned for LWE, R is unsigned for RLWE and U, S are Float for FFT opertaions in KeyGen and BlindRotate, respectively. 80 | struct KMSparams_block{T, R, U, S} <: MKTFHEparams{R, U, S} where {T<:Unsigned} 81 | d::Int64 # number of the blocks 82 | ℓ::Int64 # length of each block 83 | α::Float64 # LWE noise standard deviation 84 | 85 | f::Int64 # key-switching gadget length 86 | logD::Int64 # key-switching gadget size 87 | 88 | N::Int64 # RLWE dimension 89 | β::Float64 # RLWE noise standard deviation 90 | 91 | l_gsw::Int64 # blind-rotation gadget length 92 | logB_gsw::Int64 # blind-rotation gadget size 93 | 94 | l_lev::Int64 # LEV gadget length 95 | logB_lev::Int64 # LEV gadget size 96 | 97 | l_uni::Int64 # UniEnc gadget length 98 | logB_uni::Int64 # UniEnc gadget size 99 | 100 | k::Int64 # number of the parties 101 | end 102 | 103 | abstract type TFHEscheme{T<:Unsigned, S<:AbstractFloat} end 104 | 105 | abstract type SKscheme{T, S} <: TFHEscheme{T, S} end 106 | 107 | struct CGGI{T, S} <: SKscheme{T, S} 108 | k::Int64 109 | n::Int64 110 | N::Int64 111 | kskpar::LEVparams_digit{T} 112 | gswpar::GSWparams_digit{T} 113 | ffter::FFTransformer{S} 114 | monomial::Vector{TransNativePoly{S}} 115 | btk::BootKey_bin{T} 116 | end 117 | 118 | keygen_params(params::TFHEparams_bin{T, R, S}, ffter::FFTransformer{R}) where {T, R, S} = 119 | binary_lwekey(params.n, T), binary_ringkey(params.k, params.N, T, ffter) 120 | 121 | function getmonomial(T::Type, ffter::FFTransformer{S}) where S 122 | N = ffter.N 123 | 124 | monomial = Vector{TransNativePoly{S}}(undef, 2N) 125 | monomial[2N] = zerotransnativepoly(N, S) 126 | 127 | tmppoly = zeronativepoly(N, T) 128 | tmppoly.coeffs[1] = -T(1) 129 | 130 | @inbounds for i = 2 : N 131 | tmppoly.coeffs[i] = T(1) 132 | monomial[i-1] = fft(tmppoly, ffter) 133 | tmppoly.coeffs[i] = T(0) 134 | end 135 | 136 | tmppoly.coeffs[1] = -T(2) 137 | monomial[N] = fft(tmppoly, ffter) 138 | tmppoly.coeffs[1] = -T(1) 139 | @inbounds for i = 2 : N 140 | tmppoly.coeffs[i] = -T(1) 141 | monomial[N+i-1] = fft(tmppoly, ffter) 142 | tmppoly.coeffs[i] = T(0) 143 | end 144 | 145 | monomial 146 | end 147 | 148 | """ 149 | setup outputs LWE key, RLWE key, and Scheme. 150 | """ 151 | function setup(params::TFHEparams_bin{T, R, S}) where {T, R, S} 152 | k, n, N = params.k, params.n, params.N 153 | 154 | ffter = FFTransformer{S}(N, bits(T)) 155 | ffter_keygen = FFTransformer{R}(N, bits(T)) 156 | 157 | lwekey, ringkey = keygen_params(params, ffter_keygen) 158 | 159 | kskpar = LEVparams_digit{T}(params.f, params.logD) 160 | gswpar = GSWparams_digit{T}(k, params.l_gsw, params.logB_gsw) 161 | 162 | monomial = getmonomial(T, ffter) 163 | btk = BootKey_bin(lwekey, ringkey, kskpar, params.α, gswpar, params.β, ffter_keygen, ffter) 164 | 165 | lwekey, ringkey, CGGI{T, S}(k, n, N, kskpar, gswpar, ffter, monomial, btk) 166 | end 167 | 168 | struct LMSS{T, S} <: SKscheme{T, S} 169 | k::Int64 170 | ℓ::Int64 171 | d::Int64 172 | n::Int64 173 | N::Int64 174 | kskpar::LEVparams_digit{T} 175 | gswpar::GSWparams_digit{T} 176 | ffter::FFTransformer{S} 177 | monomial::Vector{TransNativePoly{S}} 178 | btk::BootKey_block{T} 179 | end 180 | 181 | keygen_params(params::TFHEparams_block{T, R, S}, ffter::FFTransformer{R}) where {T, R, S} = begin 182 | lwekey = block_binary_lwekey(params.d, params.ℓ, T) 183 | ringkey = partial_ringkey(params.k, params.N, lwekey, ffter) 184 | lwekey, ringkey 185 | end 186 | 187 | """ 188 | setup outputs LWE key, RLWE key, and Scheme. 189 | """ 190 | function setup(params::TFHEparams_block{T, R, S}) where {T, R, S} 191 | k, n, N = params.k, params.ℓ * params.d, params.N 192 | 193 | ffter = FFTransformer{S}(N, bits(T)) 194 | ffter_keygen = FFTransformer{R}(N, bits(T)) 195 | 196 | lwekey, ringkey = keygen_params(params, ffter_keygen) 197 | 198 | kskpar = LEVparams_digit{T}(params.f, params.logD) 199 | gswpar = GSWparams_digit{T}(k, params.l_gsw, params.logB_gsw) 200 | 201 | monomial = getmonomial(T, ffter) 202 | btk = BootKey_block(lwekey, ringkey, kskpar, params.α, gswpar, params.β, ffter_keygen, ffter) 203 | 204 | lwekey, ringkey, LMSS{T, S}(k, params.ℓ, params.d, n, N, kskpar, gswpar, ffter, monomial, btk) 205 | end 206 | 207 | abstract type MKscheme{T, S} <: TFHEscheme{T, S} end 208 | 209 | struct CCS{T, S} <: MKscheme{T, S} 210 | k::Int64 211 | n::Int64 212 | N::Int64 213 | a::TransCRS{S} 214 | kskpar::LEVparams_digit{T} 215 | unipar::Uniparams_digit{T} 216 | ffter::FFTransformer{S} 217 | monomial::Vector{TransNativePoly{S}} 218 | btk::Vector{BootKey_CCS{T, S}} 219 | end 220 | 221 | keygen_params(params::CCSparams{T, R, S}, ffter::FFTransformer{R}) where {T, R, S} = 222 | binary_lwekey(params.n, T), binary_ringkey(1, params.N, T, ffter) 223 | 224 | """ 225 | party_keygen outputs LWE key, RLWE key, and party-wise bootstrapping key. 226 | """ 227 | function party_keygen(a::CRS, params::CCSparams{T, R, S}) where {T, R, S} 228 | N = params.N 229 | 230 | kskpar = LEVparams_digit{T}(params.f, params.logD) 231 | unipar = Uniparams_digit{T}(1, params.l_uni, params.logB_uni) 232 | 233 | ffter_keygen = FFTransformer{R}(N, bits(T)) 234 | ffter = FFTransformer{S}(N, bits(T)) 235 | 236 | lwekey, ringkey = keygen_params(params, ffter_keygen) 237 | 238 | lwekey, ringkey, BootKey_CCS(lwekey, ringkey, kskpar, params.α, a, unipar, params.β, ffter_keygen, ffter) 239 | end 240 | 241 | """ 242 | setup outputs Scheme. 243 | """ 244 | function setup(a::CRS, btk::Vector{BootKey_CCS{T, S}}, params::CCSparams{T, R, S}) where {T, R, S} 245 | k, n, N = params.k, params.n, params.N 246 | ffter = FFTransformer{S}(N, bits(T)) 247 | kskpar = LEVparams_digit{T}(params.f, params.logD) 248 | unipar = Uniparams_digit{T}(1, params.l_uni, params.logB_uni) 249 | monomial = getmonomial(T, ffter) 250 | 251 | CCS{T, S}(k, n, N, fft.(a, Ref(ffter)), kskpar, unipar, ffter, monomial, btk) 252 | end 253 | 254 | abstract type KMSScheme{T, R, S} <: MKscheme{R, S} where T end 255 | 256 | struct KMS{T, R, S} <: KMSScheme{T, R, S} 257 | k::Int64 258 | n::Int64 259 | N::Int64 260 | a::TransCRS{S} 261 | kskpar::LEVparams_digit{T} 262 | ffter::FFTransformer{S} 263 | monomial::Vector{TransNativePoly{S}} 264 | btk::Vector{BootKey_KMS{T, R, S}} 265 | end 266 | 267 | keygen_params(params::KMSparams{T, R, U, S}, ffter::FFTransformer{U}) where {T, R, U, S} = 268 | binary_lwekey(params.n, T), binary_ringkey(1, params.N, R, ffter), binary_ringkey(1, params.N, R, ffter) 269 | 270 | """ 271 | party_keygen outputs LWE key, RLWE key, and party-wise bootstrapping key. 272 | """ 273 | function party_keygen(a::CRS, params::KMSparams{T, R, U, S}) where {T, R, U, S} 274 | N = params.N 275 | 276 | kskpar = LEVparams_digit{T}(params.f, params.logD) 277 | gswpar = GSWparams_digit{R}(1, params.l_gsw, params.logB_gsw) 278 | levpar = LEVparams_digit{R}(params.l_lev, params.logB_lev) 279 | unipar = Uniparams_digit{R}(1, params.l_uni, params.logB_uni) 280 | 281 | ffter_keygen = FFTransformer{U}(N, bits(R)) 282 | ffter = FFTransformer{S}(N, bits(R)) 283 | 284 | lwekey, gswkey, unikey = keygen_params(params, ffter_keygen) 285 | 286 | lwekey, gswkey, unikey, BootKey_KMS(lwekey, gswkey, unikey, kskpar, params.α, levpar, gswpar, params.β, a, unipar, ffter_keygen, ffter) 287 | end 288 | 289 | """ 290 | setup outputs Scheme. 291 | """ 292 | function setup(a::CRS, btk::Vector{BootKey_KMS{T, R, S}}, params::KMSparams{T, R, U, S}) where {T, R, U, S} 293 | k, n, N = params.k, params.n, params.N 294 | ffter = FFTransformer{S}(N, bits(R)) 295 | kskpar = LEVparams_digit{T}(params.f, params.logD) 296 | monomial = getmonomial(T, ffter) 297 | 298 | KMS{T, R, S}(k, n, N, fft(a, ffter), kskpar, ffter, monomial, btk) 299 | end 300 | 301 | struct KMS_block{T, R, S} <: KMSScheme{T, R, S} 302 | k::Int64 303 | ℓ::Int64 304 | d::Int64 305 | n::Int64 306 | N::Int64 307 | a::TransCRS{S} 308 | kskpar::LEVparams_digit{T} 309 | ffter::FFTransformer{S} 310 | monomial::Vector{TransNativePoly{S}} 311 | btk::Vector{BootKey_KMS_block{T, R, S}} 312 | end 313 | 314 | keygen_params(params::KMSparams_block{T, R, U, S}, ffter::FFTransformer{U}) where {T, R, U, S} = begin 315 | lwekey = block_binary_lwekey(params.d, params.ℓ, T) 316 | gswkey = binary_ringkey(1, params.N, R, ffter) 317 | unikey = partial_ringkey(1, params.N, R, lwekey, ffter) 318 | lwekey, gswkey, unikey 319 | end 320 | 321 | """ 322 | party_keygen outputs LWE key, RLWE key, and party-wise bootstrapping key. 323 | """ 324 | function party_keygen(a::CRS, params::KMSparams_block{T, R, U, S}) where {T, R, U, S} 325 | N = params.N 326 | 327 | kskpar = LEVparams_digit{T}(params.f, params.logD) 328 | gswpar = GSWparams_digit{R}(1, params.l_gsw, params.logB_gsw) 329 | levpar = LEVparams_digit{R}(params.l_lev, params.logB_lev) 330 | unipar = Uniparams_digit{R}(1, params.l_uni, params.logB_uni) 331 | 332 | ffter_keygen = FFTransformer{U}(N, bits(R)) 333 | ffter = FFTransformer{S}(N, bits(R)) 334 | 335 | lwekey, gswkey, unikey = keygen_params(params, ffter_keygen) 336 | 337 | lwekey, gswkey, unikey, BootKey_KMS_block(lwekey, gswkey, unikey, kskpar, params.α, levpar, gswpar, params.β, a, unipar, ffter_keygen, ffter, params.ℓ, params.d) 338 | end 339 | 340 | """ 341 | setup outputs Scheme. 342 | """ 343 | function setup(a::CRS, btk::Vector{BootKey_KMS_block{T, R, S}}, params::KMSparams_block{T, R, U, S}) where {T, R, U, S} 344 | k, ℓ, d, N = params.k, params.ℓ, params.d, params.N 345 | ffter = FFTransformer{S}(N, bits(R)) 346 | kskpar = LEVparams_digit{T}(params.f, params.logD) 347 | monomial = getmonomial(T, ffter) 348 | 349 | KMS_block{T, R, S}(k, ℓ, d, ℓ * d, N, fft(a, ffter), kskpar, ffter, monomial, btk) 350 | end 351 | 352 | function lwe_encrypt(m::Integer, key::LWEkey{T}, params::TFHEparams_bin) where T 353 | n = params.n 354 | b = unsigned(round(signed(T), gaussian(params.α))) 355 | a = rand(ChaCha20Stream(), T, n) 356 | μ = T(2) * T(m) - T(1) 357 | b += T(-reduce(+, a .* key.key) + μ << (bits(T) - 3)) 358 | LWE(b, a) 359 | end 360 | 361 | function lwe_encrypt(m::Integer, key::LWEkey{T}, params::TFHEparams_block) where T 362 | n = params.ℓ * params.d 363 | b = unsigned(round(signed(T), gaussian(params.α))) 364 | a = rand(ChaCha20Stream(), T, n) 365 | μ = T(2) * T(m) - T(1) 366 | b += T(-reduce(+, a .* key.key) + μ << (bits(T) - 3)) 367 | LWE(b, a) 368 | end 369 | 370 | function lwe_ith_encrypt(m::Integer, i::Int64, key::LWEkey{T}, params::KMSparams_block) where T 371 | n = params.ℓ * params.d 372 | b = unsigned(round(signed(T), gaussian(params.α))) 373 | a = vcat(zeros(T, n * (i-1)), rand(ChaCha20Stream(), T, n), zeros(T, n * (params.k-i))) 374 | μ = T(2) * T(m) - T(1) 375 | b += T(-reduce(+, a[n*(i-1)+1:n*i] .* key.key) + μ << (bits(T) - 3)) 376 | LWE(b, a) 377 | end 378 | 379 | function lwe_ith_encrypt(m::Integer, i::Int64, key::LWEkey{T}, params::MKTFHEparams) where T 380 | n = params.n 381 | b = unsigned(round(signed(T), gaussian(params.α))) 382 | a = vcat(zeros(T, n * (i-1)), rand(ChaCha20Stream(), T, n), zeros(T, n * (params.k-i))) 383 | μ = T(2) * T(m) - T(1) 384 | b += T(-reduce(+, a[n*(i-1)+1:n*i] .* key.key) + μ << (bits(T) - 3)) 385 | LWE(b, a) 386 | end 387 | 388 | lwe_decrypt(lwe::LWE{T}, key::LWEkey{T}) where T = 389 | divbits(lwe.b + reduce(+, key.key .* lwe.a), bits(T) - 3) == 1 390 | 391 | function lwe_decrypt(lwe::LWE{T}, keys::Vector{LWEkey{T}}, params::KMSparams_block) where T 392 | b, n = lwe.b, params.ℓ * params.d 393 | for i = eachindex(keys) 394 | b += reduce(+, keys[i].key .* lwe.a[(i-1)*n+1:i*n]) 395 | end 396 | 397 | b < (T(1) << (bits(T) - 1)) 398 | end 399 | 400 | function lwe_decrypt(lwe::LWE{T}, keys::Vector{LWEkey{T}}, params::MKTFHEparams) where T 401 | b, n = lwe.b, params.n 402 | for i = eachindex(keys) 403 | b += reduce(+, keys[i].key .* lwe.a[(i-1)*n+1:i*n]) 404 | end 405 | 406 | b < (T(1) << (bits(T) - 1)) 407 | end 408 | 409 | CRS(params::MKTFHEparams{T, R, S}) where {T, R, S} = 410 | [randnativepoly(params.N, T) for _ = 1 : params.l_uni] -------------------------------------------------------------------------------- /src/tfhe/bootstrapping.jl: -------------------------------------------------------------------------------- 1 | """ 2 | bootstrapping!(ctxt, scheme) bootstraps a binary BFV ciphertext. 3 | """ 4 | function bootstrapping!(ctxt::LWE{T}, scheme::TFHEscheme{R, S}) where {T, R, S} 5 | N, logN = scheme.N, trailing_zeros(scheme.N) 6 | 7 | # Scale up the TLWE ciphertext to a LWE ciphertext with modulus 2N. 8 | tildea = divbits.(ctxt.a, bits(T) - logN - 1) 9 | tildeb = divbits(ctxt.b, bits(T) - logN - 1) 10 | 11 | oneovereight = R(1) << (bits(R) - 3) 12 | b = zeronativepoly(N, R) 13 | if tildeb ≤ N 14 | @inbounds @simd for i = 1 : N 15 | b.coeffs[i] = i ≤ tildeb ? oneovereight : -oneovereight 16 | end 17 | else 18 | tildeb -= R(N) 19 | @inbounds @simd for i = 1 : N 20 | b.coeffs[i] = i ≤ tildeb ? -oneovereight : oneovereight 21 | end 22 | end 23 | acc = RLWE(b, [zeronativepoly(N, R) for _ = 1 : scheme.k]) 24 | 25 | blindrotate!(tildea, acc, scheme) 26 | keyswitch!(ctxt, acc, scheme) 27 | end 28 | 29 | """ 30 | Blind Rotation algorithm based on CGGI16. 31 | """ 32 | function blindrotate!(tildea::Vector{<:Unsigned}, acc::RLWE{R}, scheme::CGGI{R, S}) where {R, S} 33 | N = scheme.ffter.N 34 | 35 | param = scheme.gswpar 36 | 37 | # pre-allocate the vector to store the result of RLWE decomposition. 38 | bvec = [buffnativepoly(N, R) for _ = 1 : param.l] 39 | avec = [buffnativepoly(N, R) for _ = 1 : param.l, _ = 1 : param.k] 40 | tbvec = [bufftransnativepoly(N, S) for _ = 1 : param.l] 41 | tavec = [bufftransnativepoly(N, S) for _ = 1 : param.l, _ = 1 : param.k] 42 | 43 | # pre-allocate the temporary accumulator. 44 | acc2 = deepcopy(acc) 45 | tacc = fft(acc, scheme.ffter) 46 | 47 | for idx = eachindex(tildea) 48 | if tildea[idx] > 0 49 | # decompose the accumulator RLWE ciphertext. 50 | decompto!(bvec, acc.b, param) 51 | decompto!(avec, acc.a, param) 52 | 53 | # transform the decomposed polynomial into evaluation form. 54 | @inbounds @simd for i = eachindex(bvec) 55 | fftto!(tbvec[i], bvec[i], scheme.ffter) 56 | end 57 | @inbounds @simd for i = eachindex(avec) 58 | fftto!(tavec[i], avec[i], scheme.ffter) 59 | end 60 | 61 | # perform external product. 62 | initialise!(tacc) 63 | @inbounds for i = 1 : param.l 64 | muladdto!(tacc, tbvec[i], scheme.btk.brk[idx].basketb.stack[i]) 65 | end 66 | @inbounds for i = 1 : param.k, j = 1 : param.l 67 | muladdto!(tacc, tavec[j, i], scheme.btk.brk[idx].basketa[i].stack[j]) 68 | end 69 | 70 | # acc = acc + (X^ai - 1) acc2 71 | mul!(scheme.monomial[tildea[idx]], tacc) 72 | ifftto!(acc2, tacc, scheme.ffter) 73 | add!(acc, acc2) 74 | end 75 | end 76 | end 77 | 78 | """ 79 | Key-switch algorithm based on CGGI16. 80 | """ 81 | function keyswitch!(res::LWE{T}, acc::RLWE{T}, scheme::CGGI{T, S}) where {T, S} 82 | n, N, k, l = scheme.n, scheme.N, scheme.k, scheme.kskpar.l 83 | 84 | # initialise the resulting ciphertext and set b. 85 | initialise!(res) 86 | res.b = acc.b.coeffs[1] 87 | 88 | # iteratively perform key-switching, with on-the-fly ciphertext extraction. 89 | ajdec = Vector{T}(undef, scheme.kskpar.l) 90 | @inbounds @simd for i = 1 : k 91 | unbalanceddecompto!(ajdec, acc.a[i].coeffs[1], scheme.kskpar) 92 | @inbounds @simd for j = 1 : l 93 | if ajdec[j] > 0 94 | add!(res, scheme.btk.ksk[ajdec[j], 1, i].stack[j]) 95 | end 96 | end 97 | 98 | @inbounds @simd for idx = 2 : N 99 | unbalanceddecompto!(ajdec, T(0) - acc.a[i].coeffs[N - idx + 2], scheme.kskpar) 100 | @inbounds @simd for j = 1 : l 101 | if ajdec[j] > 0 102 | add!(res, scheme.btk.ksk[ajdec[j], idx, i].stack[j]) 103 | end 104 | end 105 | end 106 | end 107 | 108 | res 109 | end 110 | 111 | """ 112 | Blind Rotation algorithm based on LMSS23 (Fast TFHE Bootstrapping from Block Binary Distribution). 113 | """ 114 | function blindrotate!(tildea::Vector{<:Unsigned}, acc::RLWE{R}, scheme::LMSS{R, S}) where {R, S} 115 | N = scheme.ffter.N 116 | 117 | # pre-allocate the vector to store decomposed polynomials. 118 | param = scheme.gswpar 119 | bvec = [buffnativepoly(N, R) for _ = 1 : param.l] 120 | avec = [buffnativepoly(N, R) for _ = 1 : param.l, _ = 1 : param.k] 121 | tbvec = [bufftransnativepoly(N, S) for _ = 1 : param.l] 122 | tavec = [bufftransnativepoly(N, S) for _ = 1 : param.l, _ = 1 : param.k] 123 | 124 | # pre-allocate the temporary accumulators. 125 | acc2 = deepcopy(acc) 126 | tacc = fft(acc, scheme.ffter) 127 | tacc2 = deepcopy(tacc) 128 | 129 | for idx1 = 0 : scheme.d-1 130 | # decompose the accumulator. 131 | decompto!(bvec, acc.b, param) 132 | decompto!(avec, acc.a, param) 133 | 134 | # transform the decomposed polynomials into evaluation form. 135 | @inbounds @simd for i = eachindex(bvec) 136 | fftto!(tbvec[i], bvec[i], scheme.ffter) 137 | end 138 | @inbounds @simd for i = eachindex(avec) 139 | fftto!(tavec[i], avec[i], scheme.ffter) 140 | end 141 | 142 | initialise!(tacc2) 143 | for idx2 = 1 : scheme.ℓ 144 | idx = idx1 * scheme.ℓ + idx2 145 | if tildea[idx] > 0 146 | initialise!(tacc) 147 | 148 | # perform external product, and store the result in tacc. 149 | @inbounds for i = 1 : param.l 150 | muladdto!(tacc, tbvec[i], scheme.btk.brk[idx].basketb.stack[i]) 151 | end 152 | @inbounds for i = 1 : param.k, j = 1 : param.l 153 | muladdto!(tacc, tavec[j, i], scheme.btk.brk[idx].basketa[i].stack[j]) 154 | end 155 | 156 | # add to tacc2, with multiplying (X^aᵢ - 1) 157 | muladdto!(tacc2, scheme.monomial[tildea[idx]], tacc) 158 | end 159 | end 160 | 161 | # transform tacc2 back to coefficient form and add to accumulator. 162 | ifftto!(acc2, tacc2, scheme.ffter) 163 | add!(acc, acc2) 164 | end 165 | end 166 | 167 | """ 168 | Key-switch algorithm based on LMSS23. 169 | """ 170 | function keyswitch!(res::LWE{T}, acc::RLWE{T}, scheme::LMSS{T, S}) where {T, S} 171 | n, N, k, l = scheme.n, scheme.N, scheme.k, scheme.kskpar.l 172 | 173 | initialise!(res) 174 | res.b = acc.b.coeffs[1] 175 | current = 1 176 | 177 | ajdec = Vector{T}(undef, scheme.kskpar.l) 178 | for i = 1 : k 179 | if current + N ≤ n 180 | res.a[current] = acc.a[i].coeffs[1] 181 | @inbounds @simd for j = 1 : N-1 182 | res.a[current+j] = T(0) - acc.a[i].coeffs[N-j+1] 183 | end 184 | current += N 185 | 186 | continue 187 | elseif current ≤ n 188 | res.a[current] = acc.a[i].coeffs[1] 189 | @inbounds @simd for j = 1 : n - current 190 | res.a[current+j] = T(0) - acc.a[i].coeffs[N-j+1] 191 | end 192 | 193 | @inbounds @simd for idx = n-current+2 : N 194 | decompto!(ajdec, T(0) - acc.a[i].coeffs[N - idx + 2], scheme.kskpar) 195 | @inbounds @simd for j = 1 : l 196 | if signed(ajdec[j]) > 0 197 | add!(res, scheme.btk.ksk[ajdec[j], idx, i].stack[j]) 198 | elseif ajdec[j] != 0 199 | sub!(res, scheme.btk.ksk[-signed(ajdec[j]), idx, i].stack[j]) 200 | end 201 | end 202 | end 203 | 204 | current = n+1 205 | else 206 | decompto!(ajdec, acc.a[i].coeffs[1], scheme.kskpar) 207 | @inbounds @simd for j = 1 : l 208 | if signed(ajdec[j]) > 0 209 | add!(res, scheme.btk.ksk[ajdec[j], 1, i].stack[j]) 210 | elseif ajdec[j] != 0 211 | sub!(res, scheme.btk.ksk[-signed(ajdec[j]), 1, i].stack[j]) 212 | end 213 | end 214 | 215 | @inbounds @simd for idx = 2 : N 216 | decompto!(ajdec, T(0) - acc.a[i].coeffs[N - idx + 2], scheme.kskpar) 217 | @inbounds @simd for j = 1 : l 218 | if signed(ajdec[j]) > 0 219 | add!(res, scheme.btk.ksk[ajdec[j], idx, i].stack[j]) 220 | elseif ajdec[j] != 0 221 | sub!(res, scheme.btk.ksk[-signed(ajdec[j]), idx, i].stack[j]) 222 | end 223 | end 224 | end 225 | end 226 | end 227 | 228 | res 229 | end 230 | 231 | """ 232 | Blind Rotation algorithm based on CCS19. 233 | """ 234 | function blindrotate!(tildeavec::Vector{<:Unsigned}, acc::RLWE{T}, scheme::CCS{T, S}) where {T, S} 235 | tildea = reshape(tildeavec, scheme.n, scheme.k) 236 | 237 | k, n, N = scheme.k, scheme.n, scheme.N 238 | 239 | param = scheme.unipar 240 | 241 | bvec = [buffnativepoly(N, T) for _ = 1 : param.l] 242 | avec = [buffnativepoly(N, T) for _ = 1 : param.l, _ = 1 : k] 243 | tbvec = [bufftransnativepoly(N, S) for _ = 1 : param.l] 244 | tavec = [bufftransnativepoly(N, S) for _ = 1 : param.l, _ = 1 : k] 245 | 246 | v0 = buffnativepoly(N, T) 247 | tv0 = bufftransnativepoly(N, S) 248 | v0vec = [buffnativepoly(N, T) for _ = 1 : param.l] 249 | vvec = [buffnativepoly(N, T) for _ = 1 : param.l, _ = 1 : k] 250 | 251 | v = [buffnativepoly(N, T) for _ = 1 : k] 252 | tv = [bufftransnativepoly(N, S) for _ = 1 : k] 253 | tv0vec = [bufftransnativepoly(N, S) for _ = 1 : param.l] 254 | tvvec = [bufftransnativepoly(N, S) for _ = 1 : param.l, _ = 1 : k] 255 | 256 | tacc = TransRLWE(bufftransnativepoly(N, S), [bufftransnativepoly(N, S) for _ = 1 : k]) 257 | acc2 = RLWE(buffnativepoly(N, T), [buffnativepoly(N, T) for _ = 1 : k]) 258 | 259 | @inbounds for idx = 1 : k 260 | @inbounds for i = 1 : n 261 | if tildea[i, idx] > 0 262 | # hybrid product 263 | # decompose b and a of acc. 264 | decompto!(bvec, acc.b, param) 265 | decomptoith!(avec, acc.a, idx, param) 266 | 267 | # Transform bvec and avec to FFT form. 268 | @inbounds @simd for j = 1 : param.l 269 | fftto!(tbvec[j], bvec[j], scheme.ffter) 270 | end 271 | @inbounds @simd for j1 = 1 : idx 272 | @inbounds @simd for j2 = 1 : param.l 273 | fftto!(tavec[j2, j1], avec[j2, j1], scheme.ffter) 274 | end 275 | end 276 | 277 | # Compute u. 278 | initialise!(tacc) 279 | @inbounds for j = 1 : param.l 280 | muladdto!(tacc.b, tbvec[j], scheme.btk[idx].brk[i].d[j]) 281 | end 282 | @inbounds for j1 = 1 : idx, j2 = 1 : param.l 283 | muladdto!(tacc.a[j1], tavec[j2, j1], scheme.btk[idx].brk[i].d[j2]) 284 | end 285 | 286 | # Compute v. 287 | @. initialise!(tv) 288 | initialise!(tv0) 289 | @inbounds for j = 1 : param.l 290 | mulsubto!(tv0, tbvec[j], scheme.a[j]) 291 | end 292 | @inbounds for j1 = 1 : idx, j2 = 1 : param.l 293 | muladdto!(tv[j1], tavec[j2, j1], scheme.btk[j1].b[j2]) 294 | end 295 | 296 | # Inverse transform tv to v. 297 | ifftto!(v0, tv0, scheme.ffter) 298 | @inbounds for j = 1 : idx 299 | ifftto!(v[j], tv[j], scheme.ffter) 300 | end 301 | 302 | # Decompose v and transfomr. 303 | decompto!(v0vec, v0, param) 304 | decomptoith!(vvec, v, idx, param) 305 | @inbounds for j = 1 : param.l 306 | fftto!(tv0vec[j], v0vec[j], scheme.ffter) 307 | end 308 | @inbounds for j1 = 1 : idx, j2 = 1 : param.l 309 | fftto!(tvvec[j2, j1], vvec[j2, j1], scheme.ffter) 310 | end 311 | 312 | # Compute w. 313 | @inbounds for j = 1 : param.l 314 | muladdto!(tacc.b, tv0vec[j], scheme.btk[idx].brk[i].f.stack[j].b) 315 | muladdto!(tacc.a[idx], tv0vec[j], scheme.btk[idx].brk[i].f.stack[j].a[1]) 316 | end 317 | @inbounds for j1 = 1 : idx, j2 = 1 : param.l 318 | muladdto!(tacc.b, tvvec[j2, j1], scheme.btk[idx].brk[i].f.stack[j2].b) 319 | muladdto!(tacc.a[idx], tvvec[j2, j1], scheme.btk[idx].brk[i].f.stack[j2].a[1]) 320 | end 321 | 322 | mul!(scheme.monomial[tildea[i, idx]], tacc) 323 | ifftto!(acc2, tacc, scheme.ffter) 324 | add!(acc, acc2) 325 | end 326 | end 327 | end 328 | end 329 | 330 | """ 331 | Key-switch algorithm based on CCS19. 332 | """ 333 | function keyswitch!(res::LWE{T}, acc::RLWE{T}, scheme::CCS{T, S}) where {T, S} 334 | n, N, k, l = scheme.n, scheme.N, scheme.k, scheme.kskpar.l 335 | 336 | initialise!(res) 337 | res.b = acc.b.coeffs[1] 338 | 339 | # defining partial LWE ciphertext reduces the allocations. 340 | partctxt = [LWE(zero(T), zeros(T, n)) for _ = 1 : k] 341 | 342 | # key-switching is parallelizable. 343 | @threads for i = 1 : k 344 | ajdec = Vector{T}(undef, l) 345 | unbalanceddecompto!(ajdec, acc.a[i].coeffs[1], scheme.kskpar) 346 | @inbounds @simd for idx = eachindex(ajdec) 347 | if ajdec[idx] > 0 348 | add!(partctxt[i], scheme.btk[i].ksk[ajdec[idx], 1].stack[idx]) 349 | end 350 | end 351 | 352 | @inbounds @simd for j = 2 : N 353 | unbalanceddecompto!(ajdec, T(0) - acc.a[i].coeffs[N - j + 2], scheme.kskpar) 354 | @inbounds for idx = eachindex(ajdec) 355 | if ajdec[idx] > 0 356 | add!(partctxt[i], scheme.btk[i].ksk[ajdec[idx], j].stack[idx]) 357 | end 358 | end 359 | end 360 | 361 | res.b += partctxt[i].b 362 | @. res.a[(i-1)*n+1:i*n] += partctxt[i].a 363 | end 364 | end 365 | 366 | """ 367 | Blind Rotation algorithm based on our scheme. 368 | """ 369 | function blindrotate!(tildeavec::Vector{T}, acc::RLWE{R}, scheme::KMSScheme{T, R, S}) where {T, R, S} 370 | tildea = reshape(tildeavec, scheme.n, scheme.k) 371 | 372 | k = scheme.k 373 | levkey = Vector{TransRLEV{S}}(undef, k) 374 | 375 | # phase 1 : party-wise single key blind rotation. 376 | @sync for i = 1 : k 377 | @spawn levkey[i] = phase_1(i, tildea[:, i], scheme.btk[i], scheme.monomial, scheme.ffter) 378 | end 379 | 380 | # phase 2 : merge them into a single MK ciphertext. 381 | phase_2!(levkey, acc, scheme) 382 | 383 | acc 384 | end 385 | 386 | """ 387 | Phase 1 from Blind Rotation of our scheme. 388 | """ 389 | function phase_1(party::Int64, tildea::Vector{T}, btk::BootKey_KMS{T, R, S}, monomial::Vector{TransNativePoly{S}}, ffter::FFTransformer{S}) where {T, R, S} 390 | N = ffter.N 391 | 392 | bvec = [buffnativepoly(N, R) for _ = 1 : btk.gswpar.l] 393 | avec = [buffnativepoly(N, R) for _ = 1 : btk.gswpar.l] 394 | tbvec = [bufftransnativepoly(N, S) for _ = 1 : btk.gswpar.l] 395 | tavec = [bufftransnativepoly(N, S) for _ = 1 : btk.gswpar.l] 396 | 397 | # For party 1, we don't need to perform blind rotation on RLEV ciphertext. 398 | # This implementation may lead to a bigger noise variance, rather than performing blind rotation with test vector. 399 | # We implement phase 1 this way to keep the code simpler. 400 | iter = party == 1 ? 1 : btk.levpar.l 401 | 402 | stack = Vector{RLWE{R}}(undef, iter) 403 | @inbounds @simd for i = 1 : iter 404 | stack[i] = RLWE(zeronativepoly(N, R), zeronativepoly(N, R)) 405 | stack[i].b.coeffs[1] = btk.levpar.gvec[i] 406 | end 407 | acc = RLEV(stack) 408 | acc2 = RLEV(deepcopy(stack)) 409 | tacc = fft(acc, ffter) 410 | 411 | # Basically the same code to blind rotation of CGGI, but for each row of the RLEV ciphertext. 412 | @inbounds for idx = eachindex(tildea) 413 | if tildea[idx] > 0 414 | @inbounds @simd for i = 1 : iter 415 | decompto!(bvec, acc.stack[i].b, btk.gswpar) 416 | decompto!(avec, acc.stack[i].a[1], btk.gswpar) 417 | 418 | initialise!(tacc.stack[i]) 419 | 420 | @inbounds @simd for j = eachindex(bvec) 421 | fftto!(tbvec[j], bvec[j], ffter) 422 | end 423 | @inbounds @simd for j = eachindex(avec) 424 | fftto!(tavec[j], avec[j], ffter) 425 | end 426 | 427 | @inbounds for j = eachindex(bvec) 428 | muladdto!(tacc.stack[i], tbvec[j], btk.brk[idx].basketb.stack[j]) 429 | end 430 | @inbounds for j = eachindex(avec) 431 | muladdto!(tacc.stack[i], tavec[j], btk.brk[idx].basketa[1].stack[j]) 432 | end 433 | end 434 | 435 | mul!(monomial[tildea[idx]], tacc) 436 | ifftto!(acc2, tacc, ffter) 437 | add!(acc, acc2) 438 | end 439 | end 440 | 441 | fftto!(tacc, acc, ffter) 442 | tacc 443 | end 444 | 445 | """ 446 | Phase 2 from Blind Rotation of our scheme. 447 | """ 448 | function phase_2!(levkey::Vector{TransRLEV{S}}, acc::RLWE{R}, scheme::KMSScheme{T, R, S}) where {T, R, S} 449 | k, N = scheme.k, scheme.N 450 | 451 | levpar, unipar = scheme.btk[1].levpar, scheme.btk[1].unipar 452 | maxl = max(levpar.l, unipar.l) 453 | 454 | bvec = [buffnativepoly(N, R) for _ = 1 : maxl] 455 | avec = [buffnativepoly(N, R) for _ = 1 : maxl, _ = 1 : k] 456 | tbvec = [bufftransnativepoly(N, S) for _ = 1 : maxl] 457 | tavec = [bufftransnativepoly(N, S) for _ = 1 : maxl, _ = 1 : k] 458 | 459 | v = buffnativepoly(N, R) 460 | tv = bufftransnativepoly(N, S) 461 | vvec = [buffnativepoly(N, R) for _ = 1 : unipar.l] 462 | tvvec = [bufftransnativepoly(N, S) for _ = 1 : unipar.l] 463 | 464 | tx = TransRLWE(bufftransnativepoly(N, S), [bufftransnativepoly(N, S) for _ = 1 : k]) 465 | y = RLWE(buffnativepoly(N, R), [buffnativepoly(N, R) for _ = 1 : k]) 466 | ty = TransRLWE(bufftransnativepoly(N, S), [bufftransnativepoly(N, S) for _ = 1 : k]) 467 | 468 | @inbounds for idx = 1 : k 469 | # decompose b and ai of acc 470 | decompto!(bvec, acc.b, levpar) 471 | decomptoith!(avec, acc.a, idx-1, levpar) 472 | 473 | # FFT the decomposed bvec and avec 474 | @inbounds for i = 1 : levpar.l 475 | fftto!(tbvec[i], bvec[i], scheme.ffter) 476 | end 477 | @inbounds for i = 1 : idx-1, j = 1 : levpar.l 478 | fftto!(tavec[j, i], avec[j, i], scheme.ffter) 479 | end 480 | 481 | iter = idx == 1 ? 1 : levpar.l 482 | 483 | # LEV multiplication between bvec and levkey[i] 484 | initialise!(tx) 485 | @inbounds for i = 1 : iter 486 | muladdto!(tx.b, tbvec[i], levkey[idx].stack[i].b) 487 | end 488 | @inbounds for i = 1 : idx-1, j = 1 : iter 489 | muladdto!(tx.a[i], tavec[j, i], levkey[idx].stack[j].b) 490 | end 491 | 492 | # LEV multiplication between avec and levkey[i] 493 | initialise!(ty) 494 | @inbounds for i = 1 : iter 495 | muladdto!(ty.b, tbvec[i], levkey[idx].stack[i].a[1]) 496 | end 497 | @inbounds for i = 1 : idx-1, j = 1 : iter 498 | muladdto!(ty.a[i], tavec[j, i], levkey[idx].stack[j].a[1]) 499 | end 500 | 501 | ifftto!(y.b, ty.b, scheme.ffter) 502 | @inbounds for i = 1 : idx-1 503 | ifftto!(y.a[i], ty.a[i], scheme.ffter) 504 | end 505 | 506 | # hybrid product 507 | # decompose b and a of yi 508 | decompto!(bvec, y.b, unipar) 509 | decomptoith!(avec, y.a, idx-1, unipar) 510 | 511 | # Transform bvec and avec in FFT form 512 | @inbounds for i = 1 : unipar.l 513 | fftto!(tbvec[i], bvec[i], scheme.ffter) 514 | end 515 | @inbounds for i = 1 : idx-1, j = 1 : unipar.l 516 | fftto!(tavec[j, i], avec[j, i], scheme.ffter) 517 | end 518 | 519 | # Compute u. Reuse ty for memory save. 520 | initialise!(ty) 521 | @inbounds for i = 1 : unipar.l 522 | muladdto!(ty.b, tbvec[i], scheme.btk[idx].rlk.d[i]) 523 | end 524 | @inbounds for i = 1 : idx-1, j = 1 : unipar.l 525 | muladdto!(ty.a[i], tavec[j, i], scheme.btk[idx].rlk.d[j]) 526 | end 527 | 528 | # Compute v. 529 | initialise!(tv) 530 | @inbounds for i = 1 : unipar.l 531 | mulsubto!(tv, tbvec[i], scheme.a[i]) 532 | end 533 | @inbounds for i = 1 : idx - 1, j = 1 : unipar.l 534 | muladdto!(tv, tavec[j, i], scheme.btk[i].b[j]) 535 | end 536 | 537 | # Inverse transform tv to v. 538 | ifftto!(v, tv, scheme.ffter) 539 | 540 | # Decompose v and trasform. 541 | decompto!(vvec, v, unipar) 542 | @inbounds for i = 1 : unipar.l 543 | fftto!(tvvec[i], vvec[i], scheme.ffter) 544 | end 545 | 546 | # Compute w. 547 | @inbounds for i = 1 : unipar.l 548 | muladdto!(ty.b, tvvec[i], scheme.btk[idx].rlk.f.stack[i].b) 549 | muladdto!(ty.a[idx], tvvec[i], scheme.btk[idx].rlk.f.stack[i].a[1]) 550 | end 551 | 552 | # add back to x. 553 | add!(tx, ty) 554 | 555 | # Inverse transform to acc. 556 | ifftto!(acc, tx, scheme.ffter) 557 | end 558 | end 559 | 560 | """ 561 | Key-switch algorithm from our scheme. 562 | Basically the same code to CCS19, but with modulus change from R to T. 563 | """ 564 | function keyswitch!(res::LWE{T}, acc::RLWE{R}, scheme::KMS{T, R, S}) where {T, R, S} 565 | n, N, k, l = scheme.n, scheme.N, scheme.k, scheme.kskpar.l 566 | 567 | bitdiff = bits(R) - bits(T) 568 | initialise!(res) 569 | res.b = T(acc.b.coeffs[1] >> bitdiff) 570 | 571 | partctxt = [LWE(zero(T), zeros(T, n)) for _ = 1 : k] 572 | 573 | @threads for i = 1 : k 574 | ajdec = Vector{T}(undef, l) 575 | unbalanceddecompto!(ajdec, T(acc.a[i].coeffs[1] >> bitdiff), scheme.kskpar) 576 | @inbounds @simd for idx = eachindex(ajdec) 577 | if ajdec[idx] > 0 578 | add!(partctxt[i], scheme.btk[i].ksk[ajdec[idx], 1].stack[idx]) 579 | end 580 | end 581 | 582 | @inbounds @simd for j = 2 : N 583 | unbalanceddecompto!(ajdec, T(0) - T(acc.a[i].coeffs[N - j + 2] >> bitdiff), scheme.kskpar) 584 | @inbounds @simd for idx = eachindex(ajdec) 585 | if ajdec[idx] > 0 586 | add!(partctxt[i], scheme.btk[i].ksk[ajdec[idx], j].stack[idx]) 587 | end 588 | end 589 | end 590 | 591 | res.b += partctxt[i].b 592 | @. res.a[(i-1)*n+1:i*n] += partctxt[i].a 593 | end 594 | end 595 | 596 | """ 597 | Phase 1 from Blind Rotation of KMS with block binary keys. 598 | """ 599 | function phase_1(party::Int64, tildea::Vector{T}, btk::BootKey_KMS_block{T, R, S}, monomial::Vector{TransNativePoly{S}}, ffter::FFTransformer{S}) where {T, R, S} 600 | N = ffter.N 601 | 602 | bvec = [buffnativepoly(N, R) for _ = 1 : btk.gswpar.l] 603 | avec = [buffnativepoly(N, R) for _ = 1 : btk.gswpar.l] 604 | tbvec = [bufftransnativepoly(N, S) for _ = 1 : btk.gswpar.l] 605 | tavec = [bufftransnativepoly(N, S) for _ = 1 : btk.gswpar.l] 606 | 607 | # For party 1, we don't need to perform blind rotation on RLEV ciphertext. 608 | # This implementation may lead to a bigger noise variance, rather than performing blind rotation with test vector. 609 | # We implement phase 1 this way to keep the code simpler. 610 | iter = party == 1 ? 1 : btk.levpar.l 611 | 612 | stack = Vector{RLWE{R}}(undef, iter) 613 | @inbounds @simd for i = 1 : iter 614 | stack[i] = RLWE(zeronativepoly(N, R), zeronativepoly(N, R)) 615 | stack[i].b.coeffs[1] = btk.levpar.gvec[i] 616 | end 617 | acc = RLEV(stack) 618 | acc2 = RLEV(deepcopy(stack)) 619 | tacc = fft(acc, ffter) 620 | tacc2 = deepcopy(tacc) 621 | 622 | # Basically the same code to blind rotation of LMSS, but for each row of the RLEV ciphertext. 623 | @inbounds for idx1 = 0 : btk.d-1 624 | @inbounds @simd for i = 1 : iter 625 | decompto!(bvec, acc.stack[i].b, btk.gswpar) 626 | decompto!(avec, acc.stack[i].a[1], btk.gswpar) 627 | 628 | @inbounds @simd for j = eachindex(bvec) 629 | fftto!(tbvec[j], bvec[j], ffter) 630 | end 631 | @inbounds @simd for j = eachindex(avec) 632 | fftto!(tavec[j], avec[j], ffter) 633 | end 634 | 635 | initialise!(tacc2.stack[i]) 636 | for idx2 = 1 : btk.ℓ 637 | idx = idx1 * btk.ℓ + idx2 638 | if tildea[idx] > 0 639 | initialise!(tacc.stack[i]) 640 | 641 | @inbounds for j = eachindex(bvec) 642 | muladdto!(tacc.stack[i], tbvec[j], btk.brk[idx].basketb.stack[j]) 643 | end 644 | @inbounds for j = eachindex(avec) 645 | muladdto!(tacc.stack[i], tavec[j], btk.brk[idx].basketa[1].stack[j]) 646 | end 647 | 648 | muladdto!(tacc2.stack[i], monomial[tildea[idx]], tacc.stack[i]) 649 | end 650 | end 651 | end 652 | 653 | ifftto!(acc2, tacc2, ffter) 654 | add!(acc, acc2) 655 | end 656 | 657 | fftto!(tacc, acc, ffter) 658 | tacc 659 | end 660 | 661 | """ 662 | Key-switch algorithm for KMS with block binary keys. 663 | """ 664 | function keyswitch!(res::LWE{T}, acc::RLWE{R}, scheme::KMS_block{T, R, S}) where {T, R, S} 665 | n, N, k, l = scheme.n, scheme.N, scheme.k, scheme.kskpar.l 666 | 667 | bitdiff = bits(R) - bits(T) 668 | initialise!(res) 669 | res.b = T(acc.b.coeffs[1] >> bitdiff) 670 | 671 | partctxt = [LWE(zero(T), zeros(T, n)) for _ = 1 : k] 672 | 673 | @threads for i = 1 : k 674 | ajdec = Vector{T}(undef, l) 675 | 676 | partctxt[i].a[1] = T(acc.a[i].coeffs[1] >> bitdiff) 677 | @inbounds @simd for j = 2 : n 678 | partctxt[i].a[j] = T(0) - T(acc.a[i].coeffs[N-j+2] >> bitdiff) 679 | end 680 | 681 | @inbounds @simd for j = n+1 : N 682 | decompto!(ajdec, T(0) - T(acc.a[i].coeffs[N - j + 2] >> bitdiff), scheme.kskpar) 683 | @inbounds @simd for idx = eachindex(ajdec) 684 | if signed(ajdec[idx]) > 0 685 | add!(partctxt[i], scheme.btk[i].ksk[ajdec[idx], j].stack[idx]) 686 | elseif ajdec[idx] != 0 687 | sub!(partctxt[i], scheme.btk[i].ksk[-signed(ajdec[idx]), j].stack[idx]) 688 | end 689 | end 690 | end 691 | 692 | res.b += partctxt[i].b 693 | @. res.a[(i-1)*n+1:i*n] += partctxt[i].a 694 | end 695 | end --------------------------------------------------------------------------------