enhance: Refactor the cpp code to be more generic and easy to generate

This commit is contained in:
Quentin Bourgerie
2022-12-14 12:30:43 +01:00
parent 4e1197f2d7
commit 7e8792ed34
13 changed files with 131 additions and 299 deletions

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
CURVES_JSON_PATH=json/curves.json
CURVES_CPP_GEN_H=cpp/include/concrete/curves.gen.h
$(CURVES_JSON_PATH): verify_curves.py
sage verify_curves.py > $@
$(CURVES_CPP_GEN_H): cpp/gen_header.py $(CURVES_JSON_PATH)
cat $(CURVES_JSON_PATH) | python cpp/gen_header.py > $(CURVES_CPP_GEN_H)
generate-cpp-header: $(CURVES_CPP_GEN_H)
.PHONY: generate-cpp-header

View File

@@ -1,85 +0,0 @@
import subprocess
from ctypes import *
import os
import numpy as np
v0_parameters_path = "cpp"
def compile():
# Creating build directory
try:
os.mkdir(f"{v0_parameters_path}/build")
print("> Successfully created build/ directory")
except FileExistsError:
print("> build/ directory already exists")
# Compile the C++ source as a shared object
subprocess.run(
[
"g++",
"-c",
"-o",
f"{v0_parameters_path}/build/test.o",
f"{v0_parameters_path}/test.cpp",
]
)
subprocess.run(
[
"gcc",
"-shared",
"-o",
f"{v0_parameters_path}/build/libtest.so",
f"{v0_parameters_path}/build/test.o",
]
)
print("> Successfully compiled C++ source")
def load_library():
# Load library in python and define argtype / restype
lib = CDLL(f"{v0_parameters_path}/build/libtest.so")
# defining the structure at python level
class v0curves(Structure):
_fields_ = [
("securityLevel", c_int),
("linearTerm1", c_double),
("linearTerm2", c_double),
("nAlpha", c_int),
("keyFormat", c_int),
]
get = lib.security_estimator
get.argtypes = [c_int, c_int]
get.restype = POINTER(v0curves)
print("> Successfully loading shared library")
return get
def stringify_struct(struct):
return f"security_level: {struct.contents.securityLevel}, linear_term1: {struct.contents.linearTerm1}, linear_term2: {struct.contents.linearTerm2} , nAlpha: {struct.contents.nAlpha}, keyFormat: {struct.contents.keyFormat} "
def check_codegen(
curves_dict
):
# compiling as shared library
compile()
# loading library
security_estimator = load_library()
# checking everything
for security_level, key_format in curves_dict:
c_struct = security_estimator(security_level, key_format )
python_struct = curves_dict[(security_level, key_format)]
print(f"(securityLevel, keyFormat) = ({security_level, key_format} : {stringify_struct(c_struct)} ")
assert python_struct[0] == c_struct.contents.linearTerm1, f"linearTerm1: (securityLevel, keyFormat) = ({security_level, key_format} -> (Py) {python_struct[0]} (C++) {c_struct.contents.linearTerm1})"
assert python_struct[1] == c_struct.contents.linearTerm2, f"linearTerm2: (securityLevel, keyFormat) = ({security_level, key_format} -> (Py) {python_struct[1]} (C++) {c_struct.contents.linearTerm2})"
assert python_struct[2] == c_struct.contents.nAlpha, f"nAlpha: (securityLevel, keyFormat) = ({security_level, key_format} -> (Py) {python_struct[2]} (C++) {c_struct.contents.nAlpha})"
print(curves_dict)
print("> Successfully compared C++ array with Python dictionary")
if __name__ == "__main__":
from v0curves import curves_dict
compile()
load_library()
check_codegen(curves_dict)

Binary file not shown.

Binary file not shown.

View File

@@ -1,52 +0,0 @@
#include <iostream>
using namespace std;
const int num_sec_levels = 4;
const int num_key_format = 1;
typedef struct v0curves
{
int securityLevel;
double linearTerm1;
double linearTerm2;
int nAlpha;
int keyFormat;
v0curves( int securityLevel_,
double linearTerm1_,
double linearTerm2_,
int nAlpha_,
int keyFormat_)
{
securityLevel = securityLevel_;
linearTerm1 = linearTerm1_;
linearTerm2 = linearTerm2_;
nAlpha = nAlpha_;
keyFormat = keyFormat_;
}
} v0curves;
v0curves parameters[num_sec_levels][num_key_format] = {
{v0curves(1, 4.13213, 7.123123, 1, 1)},
{v0curves(2, 5.123123, 8.123123, 1, 2)},
{v0curves(3, 6.123123, 9.1231223, 1, 3)},
{v0curves(4, 10.1231, 10.123123, 1, 4)}
};
extern "C" v0curves *security_estimator(int securityLevel, int keyFormat)
{
if (securityLevel == 80 ){
return &parameters[0][keyFormat];
}
else if (securityLevel == 128 ){
return &parameters[1][keyFormat];
}
else if (securityLevel == 192 ){
return &parameters[2][keyFormat];
}
else if (securityLevel == 256 ){
return &parameters[3][keyFormat];
}
}

View File

@@ -1,139 +0,0 @@
from v0curves import curves
# define the number of security levels in curves
num_sec_levels = len(curves)
import_string = f"""
#include <iostream>
using namespace std;"""
constant_string = f"""
const int num_sec_levels = {num_sec_levels};"""
struct_string = """
typedef struct v0curves
{
int rlweDimension;
int polynomialSize;
int ciphertextModulus;
int keyFormat;
v0curves(int rlweDimension,
int polynomialSize_,
int ciphertextModulus,
int keyFormat)
{
rlweDimension = rlweDimension_;
polynomialSize = polynomialSize_;
ciphertextModulus = ciphertextModulus_;
keyFormat = keyFormat_;
}
} v0curves;"""
table_string = """
v0curves parameters[num_sec_levels] = """
get_string = """
extern "C" int security_estimator(int securityLevel, int keyFormat)
{
return &parameters[securityLevel][keyFormat];
}"""
def constructor(rlweDimension, polynomialSize, ciphertextModulus, keyFormat):
return f"v0curves({rlweDimension}, {polynomialSize}, {ciphertextModulus}, {keyFormat}),"
def fill_parameters(
# Return a string with parameters for the c++ array initialization
polynomial_size_results,
rlwe_dimension_results,
ciphertext_modulus_results,
key_format_results
):
parameters = "{}{{".format(table_string)
for security_level in range(num_sec_levels):
print(security_level)
line = "{"
try:
line += constructor(
int(polynomial_size_results[security_level]),
int(rlwe_dimension_results[security_level]),
int(ciphertext_modulus_results[security_level]),
int(key_format_results[security_level]),
)
except ValueError:
line += constructor(0, 0, 0, 0)
line = line[:-1]
line += "},"
parameters += line
parameters = parameters[:-1]
parameters += "} ;"
return parameters
def codegen(
polynomial_size_results,
rlwe_dimension_results,
ciphertext_modulus_results,
key_format_results,
):
# Generate the C++ file as a string
code = f"""
{import_string}
{constant_string}
{struct_string}
{fill_parameters(
polynomial_size_results,
rlwe_dimension_results,
ciphertext_modulus_results,
key_format_results
)}
{get_string}
"""
return code
def write_codegen(
polynomial_size_results,
rlwe_dimension_results,
ciphertext_modulus_results,
key_format_results,
):
# Create the c++ source
code = codegen(
polynomial_size_results,
rlwe_dimension_results,
ciphertext_modulus_results,
key_format_results
)
# TODO: insert correct filename here with a path
with open(f"test.cpp", "w") as f:
f.write(code)
print("> Successfully wrote C++ source to disk")
def main_codegen():
# finding parameters for V0
(
polynomial_size_results,
rlwe_dimension_results,
ciphertext_modulus_results,
key_format_results,
) = main_optimization_v0()
# code generation
write_codegen(
polynomial_size_results,
rlwe_dimension_results,
ciphertext_modulus_results,
key_format_results
)

12
cpp/gen_header.py Normal file
View File

@@ -0,0 +1,12 @@
import sys, json;
def print_curve(data):
print(f'\tSecurityCurve({data["security_level"]},{data["slope"]}, {data["bias"]}, {data["minimal_lwe_dimension"]}, KeyFormat::BINARY),')
def print_cpp_curves_declaration(datas):
print("std::vector<SecurityCurve> curves {")
for data in datas:
print_curve(data)
print("}\n")
print_cpp_curves_declaration(json.load(sys.stdin))

View File

@@ -0,0 +1,12 @@
std::vector<SecurityCurve> curves {
SecurityCurve(80,-0.0404263311936459, 1.660978864143658, 450, KeyFormat::BINARY),
SecurityCurve(96,-0.03414780360867054, 2.0173102586603733, 450, KeyFormat::BINARY),
SecurityCurve(112,-0.02967013708113588, 2.16246371408387, 450, KeyFormat::BINARY),
SecurityCurve(128,-0.026405028765226296, 2.482642269104389, 450, KeyFormat::BINARY),
SecurityCurve(144,-0.023821437305989134, 2.7177789440636673, 450, KeyFormat::BINARY),
SecurityCurve(160,-0.021743582187160406, 2.9388105484933504, 498, KeyFormat::BINARY),
SecurityCurve(176,-0.019904056582117705, 2.8161252801542673, 551, KeyFormat::BINARY),
SecurityCurve(192,-0.018610403247590064, 3.2996236848399008, 606, KeyFormat::BINARY),
SecurityCurve(256,-0.014606812351714961, 3.8493629234693145, 826, KeyFormat::BINARY),
}

View File

@@ -0,0 +1,67 @@
// Part of the Concrete Compiler Project, under the BSD3 License with Zama
// Exceptions. See
// https://github.com/zama-ai/concrete-compiler-internal/blob/main/LICENSE.txt
// for license information.
#ifndef CONCRETELANG_SUPPORT_V0CURVES_H_
#define CONCRETELANG_SUPPORT_V0CURVES_H_
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <vector>
namespace concrete {
enum KeyFormat {
BINARY,
};
/// @brief SecurityCurves represents a curves of security
struct SecurityCurve {
/// @brief Number of bits of security
int bits;
/// @brief A term of the curve
double slope;
/// @brief A term of the curve
double bias;
/// @brief The minimal secure n
int minimalLweDimension;
/// @brief The format of the key
int keyFormat;
SecurityCurve() = delete;
SecurityCurve(int bits, double slope, double bias, int minimalLweDimension,
KeyFormat keyFormat)
: bits(bits), slope(slope), bias(bias),
minimalLweDimension(minimalLweDimension), keyFormat(keyFormat) {}
/// @brief Returns the secure encryption variance for glwe ciphertexts
/// @param glweDimension The dimension of the glwe
/// @param polynomialSize The size of the polynom of the glwe
/// @param logQ The log of q
/// @return The secure encryption variances
double getVariance(int glweDimension, int polynomialSize, int logQ) {
auto a = std::pow(
2, (slope * glweDimension * polynomialSize + bias) * 2);
auto b = std::pow(2, -2 * (logQ - 2));
return a > b ? a : b;
}
};
#include "curves.gen.h"
/// @brief Return the security curve for a given level and a key format.
/// @param bitsOfSecurity The number of bits of security
/// @param keyFormat The format of the key
/// @return The security curve or nullptr if the curve is not found.
SecurityCurve *getSecurtityCurve(int bitsOfSecurity, KeyFormat keyFormat) {
std::find_if(curves.begin(), curves.end(), [&](SecurityCurve c) {
return c.bits == bitsOfSecurity && c.keyFormat == keyFormat;
});
}
} // namespace concrete
#endif

View File

@@ -1,9 +0,0 @@
curves = [
(80, -0.04047677865612648, 1.1433465085639063, 160, 0),
(128, -0.026374888765705498, 2.012143923330495, 256, 0),
(192, -0.018504919354426233, 2.6634073426215843, 384, 0),
(256, -0.014327640360322604, 2.899270827311091, 781, 0),
]
curves_dict = {(tuple[0], tuple[-1]): tuple[1:4] for tuple in curves}

1
json/curves.json Normal file
View File

@@ -0,0 +1 @@
[{"slope": -0.0404263311936459, "bias": 1.660978864143658, "security_level": 80, "minimal_lwe_dimension": 450}, {"slope": -0.03414780360867054, "bias": 2.0173102586603733, "security_level": 96, "minimal_lwe_dimension": 450}, {"slope": -0.02967013708113588, "bias": 2.16246371408387, "security_level": 112, "minimal_lwe_dimension": 450}, {"slope": -0.026405028765226296, "bias": 2.482642269104389, "security_level": 128, "minimal_lwe_dimension": 450}, {"slope": -0.023821437305989134, "bias": 2.7177789440636673, "security_level": 144, "minimal_lwe_dimension": 450}, {"slope": -0.021743582187160406, "bias": 2.9388105484933504, "security_level": 160, "minimal_lwe_dimension": 498}, {"slope": -0.019904056582117705, "bias": 2.8161252801542673, "security_level": 176, "minimal_lwe_dimension": 551}, {"slope": -0.018610403247590064, "bias": 3.2996236848399008, "security_level": 192, "minimal_lwe_dimension": 606}, {"slope": -0.014606812351714961, "bias": 3.8493629234693145, "security_level": 256, "minimal_lwe_dimension": 826}]

Binary file not shown.

View File

@@ -1,5 +1,6 @@
import numpy as np
from sage.all import save, load, ceil
import json
def sort_data(security_level):
@@ -60,37 +61,49 @@ def verify_curve(security_level, a=None, b=None):
# step 3. for each n, check whether we satisfy the table
n_min = max(2 * security_level, 450, X["{}".format(security_level)][-1][0])
print(n_min)
print(n_max)
#print(n_min)
#print(n_max)
for n in range(n_max, n_min, -1):
model_sd = f_model(a, b, n)
table_sd = f_table(X["{}".format(security_level)], n)
print(n, table_sd, model_sd, model_sd >= table_sd)
#print(n, table_sd, model_sd, model_sd >= table_sd)
if table_sd > model_sd:
print("MODEL FAILS at n = {}".format(n))
return "FAIL"
#print("MODEL FAILS at n = {}".format(n))
return False
return "PASS", n_min
return True, n_min
def generate_and_verify(security_levels, log_q, name="verified_curves"):
data = []
success = []
fail = []
for sec in security_levels:
print("WE GO FOR {}".format(sec))
#print("WE GO FOR {}".format(sec))
# generate the model for security level sec
(a_sec, b_sec) = generate_curve(sec)
# verify the model for security level sec
res = verify_curve(sec, a_sec, b_sec)
(status, n_alpha) = verify_curve(sec, a_sec, b_sec)
# append the information into a list
data.append((a_sec, b_sec - log_q, sec, res[0], res[1]))
save(data, "{}.sobj".format(name))
x = {"slope": a_sec, "bias": b_sec - log_q, "security_level": sec, "minimal_lwe_dimension": n_alpha}
if status:
success.append(x)
else:
fail.append(x)
return data
save(success, "{}.sobj".format(name))
return success, fail
data = generate_and_verify([80, 96, 112, 128, 144, 160, 176, 192, 256], log_q=64)
print(data)
(success, fail) = generate_and_verify([80, 96, 112, 128, 144, 160, 176, 192, 256], log_q=64)
if (fail):
print("FAILURE: Fail to verify the following curves")
print(json.dumps(fail))
exit(1)
print(json.dumps(success))