How to Program

The poseidon/examples directory contains three subdirectories BFV, BGV and CKKS , which contain basic example programming for different schemes.

All the BFV, BGV and CKKS examples support different security levels and polynomial degrees. Additionally, they can be executed in both software mode and hardware mode. If users wish to execute the examples in various scenarios, they can customize the security parameters to meet the requirement.

In this documentation, we will introduce an example based on BFV to show how to program with Poseidon.


Prerequisites

Poseidon supports three different key switch schemes -- BV, GHS and HYBRID, of which the difference lies in the setting of modulus chain.

In BV key switch scheme, the size of modulus chain P is 1 and the size of modulus chain Q is unlimited.

In GHS key switch scheme, the size of modulus chain P is limited to be equal to the size of modulus chain Q.

In HYBRID key switch scheme, the size of modulus chain P and Q is unlimited.

For now, Poseidon implement BV key switch scheme for BFV, BGV and CKKS.


Choose the Device Type

At the beginning of the program, we need to decide whether to run the program on the CPU or HPU.

// To run the program with CPU, set the DEVICE_SOFTWARE
PoseidonFactory::get_instance()->set_device_type(DEVICE_SOFTWARE);

// To run the program with HPU, set the DEVICE_HARDWARE
PoseidonFactory::get_instance()->set_device_type(DEVICE_HARDWARE);

Pay attention! Hardware mode (DEVICE_HARDWARE) is supported only when both the Poseidon hardware library and the HPU card are installed.


Parameter Specification & Encryption Class Initialization

Next, we need to specify the parameters which are the encryption scheme, polynomial degree and security level to initiate the context.

// Here we choose the homomorphic encryption scheme to be BFV, 
// the polynomial degree to be 16384 and the security level to be tc128
ParametersLiteralDefault bfv_param_literal(BFV, 16384, poseidon::sec_level_type::tc128);

// Create the context with the customized parameter above    
PoseidonContext context = PoseidonFactory::get_instance()->create_poseidon_context(bfv_param_literal);

With the generated context, we could construct the key generator, encoder, encryptor, decryptor and evaluator class.

BatchEncoder enc(context);

KeyGenerator keygen(context);
PublicKey public_key;
GaloisKeys galois_keys;
RelinKeys relin_keys;
keygen.create_public_key(public_key);
keygen.create_galois_keys(galois_keys);
keygen.create_relin_keys(relin_keys);

Encryptor encryptor(context, public_key);
Decryptor decryptor(context, keygen.secret_key());

// Before performing the homomorphic computation, the evaluator class should be specified as EvaluatorBfvBase
std::shared_ptr<EvaluatorBfvBase> bfv_eva = PoseidonFactory::get_instance()->create_bfv_evaluator(context);


Encoding and Encryption

We could encode the message with BatchEncoder and encrypt the plaintext with Encryptor .

Plaintext plain1;
Ciphertext ciph1;
std::vector<int> message1 = {1, 2, 3};

enc.encode(message1, plain1);
encryptor.encrypt(plain1, ciph1);

The message type differs between the schemes. Integer is supported in BFV and BGV, while double and std::complex are supported in CKKS.


Homomorphic Computation

We could execute the homomorphic computation by calling the member function of the Evaluator class.

Ciphertext ciph1, ciph2, ciph3;

// homomorphic addition
bfv_eva->add(ciph1, ciph2, ciph1);
// homomorphic multiplication and relinearization
bfv_eva->multiply_relin(ciph1, ciph2, ciph1);
// mod switching
bfv_eva->drop_modulus_to_next(ciph1, ciph1);


Decoding and Decryption

After all the homomorphic computations are done, we could decrypt the ciphertext result with Decryptor and decode the plaintext result with Encoder.

In the end, we get the homomorphic computation result message_res which should be the same as direct computation result on the message.

decryptor.decrypt(ciph1, plain_res);
enc.decode(plain_res, message_res);


Timer, Accuracy and Precision

Poseidon provides some useful gadgets for users.


  • TimeStacs , the timer to record the total time spent on the homomorphic computation.
Timestacs timestacs;
timestacs.start();
timestacs.end();
timestacs.print_time("TIME : ");


  • Accuracy

In BFV and BGV examples, the result of homomorphic computation should be the same as the result calculated directly.

for (auto i = 0; i < message_want.size(); i++)
{
    printf("source_data[%d] : %ld\n", i, message1[i]);
    printf("result_data[%d] : %ld\n", i, message_res[i]);
}

In CKKS examples, the result of homomorphic computation should be approximate to the result calculated directly, which may differ a little in decimal part.

for (auto i = 0; i < 4; i++)
{
    printf("source_data[%d] : %.10lf + %.10lf I\n", i, message_want[i].real(), message_want[i].imag());
    printf("result_data[%d] : %.10lf + %.10lf I\n", i, message_res[i].real(), message_res[i].imag());
}


  • GetPrecisionStats, the function for measurement of precision for CKKS

In CKKS examples, GetPrecisionStats calculates the mean square error of the result.

util::GetPrecisionStats(message_want, message_res);