def test_reduction_balancer(self): """ Check that reductions are balanced under balance_reductions=true """ prog = EvaProgram('ReductionTree', vec_size=16384) with prog: x1 = Input('x1') x2 = Input('x2') x3 = Input('x3') x4 = Input('x4') Output('y', (x1 * (x2 * (x3 * x4))) + (x1 + (x2 + (x3 + x4)))) prog.set_output_ranges(20) prog.set_input_scales(60) progc, params, signature = self.assert_compiles_and_matches_reference( prog, config={ 'rescaler': 'always', 'balance_reductions': 'false', 'warn_vec_size': 'false' }) self.assertEqual(params.prime_bits, [60, 20, 60, 60, 60, 60]) progc, params, signature = self.assert_compiles_and_matches_reference( prog, config={ 'rescaler': 'always', 'balance_reductions': 'true', 'warn_vec_size': 'false' }) self.assertEqual(params.prime_bits, [60, 20, 60, 60, 60])
def test_unary_ops(self): """ Test all unary ops """ for unOp in [lambda x: x, lambda x: -x, lambda x: x**3, lambda x: 42]: for enc in [False, True]: prog = EvaProgram('UnOp', vec_size=64) with prog: x = Input('x', enc) Output('y', unOp(x)) prog.set_output_ranges(20) prog.set_input_scales(30) self.assert_compiles_and_matches_reference( prog, config={'warn_vec_size': 'false'})
def test_rotations(self): """ Test all rotations """ for rotOp in [lambda x, r: x << r, lambda x, r: x >> r]: for enc in [False, True]: for rot in range(-2, 2): prog = EvaProgram('RotOp', vec_size=8) with prog: x = Input('x') Output('y', rotOp(x, rot)) prog.set_output_ranges(20) prog.set_input_scales(30) self.assert_compiles_and_matches_reference( prog, config={'warn_vec_size': 'false'})
def test_unencrypted_computation(self): """ Test computation on unencrypted values """ for enc1 in [False, True]: for enc2 in [False, True]: prog = EvaProgram('UnencryptedInputs', vec_size=128) with prog: x1 = Input('x1', enc1) x2 = Input('x2', enc2) Output('y', pow(x2, 3) + x1 * x2) prog.set_output_ranges(20) prog.set_input_scales(30) self.assert_compiles_and_matches_reference( prog, config={'warn_vec_size': 'false'})
def test_unsupported_security_level(self): """ Check that unsupported security levels error out """ prog = EvaProgram('SecurityLevel', vec_size=512) with prog: x = Input('x') Output('y', 5 * x * x + 3 * x + x << 12 + 10) prog.set_output_ranges(20) prog.set_input_scales(30) self.assert_compiles_and_matches_reference(prog, config={ 'security_level': '1024', 'warn_vec_size': 'false' })
def test_sobel_configs(self): """ Check accuracy of Sobel filter on random image with various compiler configurations """ def convolutionXY(image, width, filter): for i in range(len(filter)): for j in range(len(filter[0])): rotated = image << (i * width + j) horizontal = rotated * filter[i][j] vertical = rotated * filter[j][i] if i == 0 and j == 0: Ix = horizontal Iy = vertical else: Ix += horizontal Iy += vertical return Ix, Iy h = 90 w = 90 sobel = EvaProgram('sobel', vec_size=2**(math.ceil(math.log(h * w, 2)))) with sobel: image = Input('image') sobel_filter = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]] a1 = 2.2137874823876622 a2 = -1.0984324107372518 a3 = 0.17254603006834726 conv_hor, conv_ver = convolutionXY(image, w, sobel_filter) x = conv_hor**2 + conv_ver**2 Output('image', x * a1 + x**2 * a2 + x**3 * a3) sobel.set_input_scales(45) sobel.set_output_ranges(20) for rescaler in ['lazy_waterline', 'eager_waterline', 'always']: for balance_reductions in ['true', 'false']: self.assert_compiles_and_matches_reference( sobel, config={ 'rescaler': rescaler, 'balance_reductions': balance_reductions })
def test_seal_no_throw_on_transparent(self): """ Check that SEAL is compiled with -DSEAL_THROW_ON_TRANSPARENT_CIPHERTEXT=OFF An HE compiler cannot in general work with transparent ciphertext detection turned on because it is not possible to statically detect all situations that result in them. For example, x1-x2 is transparent only if the user gives the same ciphertext as both inputs.""" prog = EvaProgram('Transparent', vec_size=4096) with prog: x = Input('x') Output('y', x - x + x * 0) prog.set_output_ranges(20) prog.set_input_scales(30) self.assert_compiles_and_matches_reference( prog, config={'warn_vec_size': 'false'})
def test_high_inner_term_scale(self): """ Test lazy waterline rescaler with a program causing a high inner term scale This test was added for a bug that was an interaction between rescaling not being inserted (causing high scales to be accumulated) and parameter selection not handling high scales in inner terms.""" prog = EvaProgram('HighInnerTermScale', vec_size=4) with prog: x1 = Input('x1') x2 = Input('x2') Output('y', x1 * x1 * x2) prog.set_output_ranges(20) prog.set_input_scales(60) self.assert_compiles_and_matches_reference( prog, config={'rescaler': 'lazy_waterline'})
def test_bin_ops(self): """ Test all binary ops """ for binOp in [ lambda a, b: a + b, lambda a, b: a - b, lambda a, b: a * b ]: for enc1 in [False, True]: for enc2 in [False, True]: prog = EvaProgram('BinOp', vec_size=64) with prog: a = Input('a', enc1) b = Input('b', enc2) Output('y', binOp(a, b)) prog.set_output_ranges(20) prog.set_input_scales(30) self.assert_compiles_and_matches_reference( prog, config={'warn_vec_size': 'false'})
def test_large_and_small(self): """ Check that a ciphertext with very large and small values decodes accurately This test was added to track a common bug in CKKS implementations, where double precision floating points used in decoding fail to provide good accuracy for small values in ciphertexts when other very large values are present.""" prog = EvaProgram('LargeAndSmall', vec_size=4) with prog: x = Input('x') Output('y', pow(x, 8)) prog.set_output_ranges(60) prog.set_input_scales(60) inputs = {'x': [0, 1, 10, 100]} self.assert_compiles_and_matches_reference( prog, inputs, config={'warn_vec_size': 'false'})
def test_output_rescaled(self): """ Check that the lazy waterline policy rescales outputs This test was added for a bug where outputs could be returned with more primes in their modulus than necessary, which causes them to take more space when serialized.""" prog = EvaProgram('OutputRescaled', vec_size=4) with prog: x = Input('x') Output('y', x * x) prog.set_output_ranges(20) prog.set_input_scales(60) compiler = CKKSCompiler(config={ 'rescaler': 'lazy_waterline', 'warn_vec_size': 'false' }) prog, params, signature = compiler.compile(prog) self.assertEqual(params.prime_bits, [60, 20, 60, 60])
def compile(): print('Compile time') chi_squared = EvaProgram('Chi Squared', vec_size=1) with chi_squared: n0 = Input('n0') n1 = Input('n1') n2 = Input('n2') Output('alpha', (4 * n0 * n2 - n1**2)**2) Output('beta1', 2 * ((2 * n0 + n1)**2)) Output('beta2', (2 * n0 + n1) * (2 * n2 + n1)) Output('beta3', 2 * (2 * n2 + n1)**2) chi_squared.set_output_ranges(60) chi_squared.set_input_scales(60) compiler = CKKSCompiler() chi_squared, params, signature = compiler.compile(chi_squared) save(chi_squared, 'chi_squared.eva') save(params, 'chi_squared.evaparams') save(signature, 'chi_squared.evasignature')
def test_security_levels(self): """ Check that all supported security levels work """ security_levels = ['128', '192', '256'] quantum_safety = ['false', 'true'] for s in security_levels: for q in quantum_safety: prog = EvaProgram('SecurityLevel', vec_size=512) with prog: x = Input('x') Output('y', 5 * x * x + 3 * x + x << 12 + 10) prog.set_output_ranges(20) prog.set_input_scales(30) self.assert_compiles_and_matches_reference( prog, config={ 'security_level': s, 'quantum_safe': q, 'warn_vec_size': 'false' })
def test_regression(self): """ Test batched compilation and execution of multiple linear regression programs """ linreg = EvaProgram('linear_regression', vec_size=2048) with linreg: p = 63 x = [Input(f'x{i}') for i in range(p)] e = Input('e') b0 = 6.56 b = [i * 0.732 for i in range(p)] y = e + b0 for i in range(p): t = x[i] * b[i] y += t Output('y', y) linreg.set_input_scales(40) linreg.set_output_ranges(30) linreg_inputs = { 'e': [(linreg.vec_size - i) * 0.001 for i in range(linreg.vec_size)] } for i in range(p): linreg_inputs[f'x{i}'] = [ i * j * 0.01 for j in range(linreg.vec_size) ] polyreg = EvaProgram('polynomial_regression', vec_size=4096) with polyreg: p = 4 x = Input('x') e = Input('e') b0 = 6.56 b = [i * 0.732 for i in range(p)] y = e + b0 for i in range(p): x_i = x for j in range(i): x_i = x_i * x t = x_i * b[i] y += t Output('y', y) polyreg.set_input_scales(40) polyreg.set_output_ranges(30) polyreg_inputs = { 'x': [i * 0.01 for i in range(polyreg.vec_size)], 'e': [(polyreg.vec_size - i) * 0.001 for i in range(polyreg.vec_size)], } multireg = EvaProgram('multivariate_regression', vec_size=2048) with multireg: p = 63 k = 4 x = [Input(f'x{i}') for i in range(p)] e = [Input(f'e{j}') for j in range(k)] b0 = [j * 0.56 for j in range(k)] b = [[k * i * 0.732 for i in range(p)] for j in range(k)] y = [0 for j in range(k)] for j in range(k): y[j] = e[j] + b0[j] for i in range(p): t = x[i] * b[j][i] y[j] += t for j in range(k): Output(f'y{j}', y[j]) multireg.set_input_scales(40) multireg.set_output_ranges(30) multireg_inputs = {} for i in range(p): multireg_inputs[f'x{i}'] = [ i * j * 0.01 for j in range(multireg.vec_size) ] for j in range(k): multireg_inputs[f'e{j}'] = [(multireg.vec_size - i) * j * 0.001 for i in range(multireg.vec_size)] compiler = CKKSCompiler(config={'warn_vec_size': 'false'}) for prog, inputs in [(linreg, linreg_inputs), (polyreg, polyreg_inputs), (multireg, multireg_inputs)]: compiled_prog, params, signature = compiler.compile(prog) public_ctx, secret_ctx = generate_keys(params) enc_inputs = public_ctx.encrypt(inputs, signature) enc_outputs = public_ctx.execute(compiled_prog, enc_inputs) outputs = secret_ctx.decrypt(enc_outputs, signature) reference = evaluate(compiled_prog, inputs) self.assertTrue(valuation_mse(outputs, reference) < 0.01)
def test_horizontal_sum(self): """ Test eva.std.numeric.horizontal_sum """ for enc in [True, False]: prog = EvaProgram('HorizontalSum', vec_size=2048) with prog: x = Input('x', is_encrypted=enc) y = horizontal_sum(x) Output('y', y) prog.set_output_ranges(25) prog.set_input_scales(33) self.assert_compiles_and_matches_reference( prog, config={'warn_vec_size': 'false'}) prog = EvaProgram('HorizontalSumConstant', vec_size=2048) with prog: y = horizontal_sum([1 for _ in range(prog.vec_size)]) Output('y', y) prog.set_output_ranges(25) prog.set_input_scales(33) self.assert_compiles_and_matches_reference( prog, config={'warn_vec_size': 'false'})
rotated = image << (i * width + j) horizontal = rotated * filter[i][j] vertical = rotated * filter[j][i] if i == 0 and j == 0: Ix = horizontal Iy = vertical else: Ix += horizontal Iy += vertical return Ix, Iy h = 64 w = 64 sobel = EvaProgram('sobel', vec_size=h * w) with sobel: image = Input('image') sobel_filter = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]] a1 = 2.2137874823876622 a2 = -1.0984324107372518 a3 = 0.17254603006834726 conv_hor, conv_ver = convolutionXY(image, w, sobel_filter) conv_hor2 = conv_hor**2 conv_ver2 = conv_ver**2 dsq = conv_hor2 + conv_ver2 dsq2 = dsq * dsq
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT license. from eva import EvaProgram, Input, Output, evaluate, save, load from eva.ckks import CKKSCompiler from eva.seal import generate_keys from eva.metric import valuation_mse import numpy as np ################################################# print('Compile time') poly = EvaProgram('Polynomial', vec_size=8) with poly: x = Input('x') Output('y', 3 * x**2 + 5 * x - 2) poly.set_output_ranges(20) poly.set_input_scales(20) compiler = CKKSCompiler() poly, params, signature = compiler.compile(poly) save(poly, 'poly.eva') save(params, 'poly.evaparams') save(signature, 'poly.evasignature') ################################################# print('Key generation time') params = load('poly.evaparams')
def test_serialization(self): """ Test (de)serialization and check that results stay the same """ poly = EvaProgram('Polynomial', vec_size=4096) with poly: x = Input('x') Output('y', 3 * x**2 + 5 * x - 2) poly.set_output_ranges(20) poly.set_input_scales(30) inputs = {'x': [i for i in range(poly.vec_size)]} reference = evaluate(poly, inputs) compiler = CKKSCompiler(config={'warn_vec_size': 'false'}) poly, params, signature = compiler.compile(poly) with tempfile.TemporaryDirectory() as tmp_dir: tmp_path = lambda x: os.path.join(tmp_dir, x) save(poly, tmp_path('poly.eva')) save(params, tmp_path('poly.evaparams')) save(signature, tmp_path('poly.evasignature')) # Key generation time params = load(tmp_path('poly.evaparams')) public_ctx, secret_ctx = generate_keys(params) save(public_ctx, tmp_path('poly.sealpublic')) save(secret_ctx, tmp_path('poly.sealsecret')) # Runtime on client signature = load(tmp_path('poly.evasignature')) public_ctx = load(tmp_path('poly.sealpublic')) encInputs = public_ctx.encrypt(inputs, signature) save(encInputs, tmp_path('poly_inputs.sealvals')) # Runtime on server poly = load(tmp_path('poly.eva')) public_ctx = load(tmp_path('poly.sealpublic')) encInputs = load(tmp_path('poly_inputs.sealvals')) encOutputs = public_ctx.execute(poly, encInputs) save(encOutputs, tmp_path('poly_outputs.sealvals')) # Runtime back on client secret_ctx = load(tmp_path('poly.sealsecret')) encOutputs = load(tmp_path('poly_outputs.sealvals')) outputs = secret_ctx.decrypt(encOutputs, signature) reference_compiled = evaluate(poly, inputs) self.assertTrue( valuation_mse(reference, reference_compiled) < 0.0000000001) self.assertTrue(valuation_mse(outputs, reference) < 0.01)