SEAL库 例3之levels.cpp解析


SEAL/native/examples/3_levels.cpp


void example_levels()

*print_example_banner("Example: Levels");*

在本例中,我们描述了BFV和ckk中的“level”概念,以及在Microsoft SEAL中表示它们的相关对象object。

在Microsoft SEAL中,一组加密参数(不包括随机数生成器)由参数的SHA-3 哈希唯一标识。这个哈希称为“parms_id”,可以在任何时候方便地访问和打印。一旦任何参数改变,哈希值就会改变。

当创建SEALContext从给定EncryptionParameters实例时,Microsoft SEAL自动创建一个所谓的“模数转换链”,这是一个来源于原始set的 a chain of 其他加密参数。模数转换链中的参数与原始参数是相同的除了系数模量的大小沿着链向下递减。更准确地说,链中的每个参数集都试图从前一个集合中移除最后一个系数模量素数;这种情况一直持续到参数集不再有效为止(例如,plain_modules大于剩余的coeff_modules)。很容易遍历链并访问所有参数集。此外,链中的每个参数集都有一个“链索引”,该索引表示其在链中的位置,因此最后一个参数集的索引为0。我们说,一组加密参数或携带这些加密参数的对象在链中的级别高于另一组参数,如果它的链索引更大,例如它处于链的较早阶段。

在创建SEALContext时链中的每一组参数都包含执行的惟一的预计算,并存储在SEALContext::ContextData对象中。该链基本上是一个SEALContext::ContextData对象的链表,可以在任何时候通过SEALContext轻松地访问它。每个节点都可以通过其特定加密参数的parms_id来标识(poly_modulus_degree保持不变,但coeff_modul_modules会变化。

EncryptionParameters parms(scheme_type::BFV);

size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);

在这个例子中,我们使用了一个自定义的coeff_modules,包含大小为50、30、30、50和50位的5个素数。请注意,这仍然是OK根据在“1_bfv_basic .cpp”中的解释。事实上,

CoeffModulus:: MaxBitCount (poly_modulus_degree)

返回218(小于50+30+30+50+50=210)。

由于模交换链的关系,5个素数的顺序是显著的。最后一个素数有一个特殊的含义,我们称之为“特殊素数”。因此,模量转换链中的第一个参数集是唯一涉及到特殊素数的参数集。所有密钥对象,如SecretKey,都是在这个最高级别创建的。所有数据对象(如密文)只能在较低的级别上。特殊模量应该与coeff_modules中其他质数中最大的一样大,尽管这不是严格的要求。

          special prime +---------+
                                  |
                                  v
coeff_modulus: { 50, 30, 30, 50, 50 }  +---+  Level 4 (all keys; `key level')
                                           |
                                           |
    coeff_modulus: { 50, 30, 30, 50 }  +---+  Level 3 (highest `data level')
                                           |
                                           |
        coeff_modulus: { 50, 30, 30 }  +---+  Level 2
                                           |
                                           |
            coeff_modulus: { 50, 30 }  +---+  Level 1
                                           |
                                           |
                coeff_modulus: { 50 }  +---+  Level 0 (lowest level)
    parms.set_coeff_modulus(CoeffModulus::Create(
        poly_modulus_degree, { 50, 30, 30, 50, 50 }));

    /*
    In this example the plain_modulus does not play much of a role; we choose
    some reasonable value.
    */
    parms.set_plain_modulus(1 << 20);

    auto context = SEALContext::Create(parms);
    print_parameters(context);
    cout << endl;

有一些方便的方法为一些最重要的级别来访问这些sealcontext::ContextData:

    SEALContext::key_context_data(): access to key level ContextData
    SEALContext::first_context_data(): access to highest data level ContextData
    SEALContext::last_context_data(): access to lowest level ContextData

我们遍历链并为每个参数集打印parms_id:

print_line(__LINE__);
cout << "Print the modulus switching chain." << endl;
 /*
First print the key level parameter information.
*/
auto context_data = context->key_context_data();
cout << "----> Level (chain index): " << context_data->chain_index();
cout << " ...... key_context_data()" << endl;
cout << "      parms_id: " << context_data->parms_id() << endl;
cout << "      coeff_modulus primes: ";
cout << hex;
for(const auto &prime : context_data->parms().coeff_modulus())
{
    cout << prime.value() << " ";
}
cout << dec << endl;
cout << "\\" << endl;
cout << " \\-->";

/*
Next iterate over the remaining (data) levels.
*/
context_data = context->first_context_data();
while (context_data)
{
    cout << " Level (chain index): " << context_data->chain_index();
    if (context_data->parms_id() == context->first_parms_id())
    {
        cout << " ...... first_context_data()" << endl;
    }
    else if (context_data->parms_id() == context->last_parms_id())
    {
        cout << " ...... last_context_data()" << endl;
    }
    else
    {
        cout << endl;
    }
    cout << "      parms_id: " << context_data->parms_id() << endl;
    cout << "      coeff_modulus primes: ";
    cout << hex;
    for(const auto &prime : context_data->parms().coeff_modulus())
    {
        cout << prime.value() << " ";
    }
    cout << dec << endl;
    cout << "\\" << endl;
    cout << " \\-->";

    /*
    Step forward in the chain.
    */
    context_data = context_data->next_context_data();
}
cout << " End of chain reached" << endl << endl;

/*
We create some keys and check that indeed they appear at the highest level.
*/
KeyGenerator keygen(context);
auto public_key = keygen.public_key();
auto secret_key = keygen.secret_key();
auto relin_keys = keygen.relin_keys();
auto galois_keys = keygen.galois_keys();
print_line(__LINE__);
cout << "Print the parameter IDs of generated elements." << endl;
cout << "    + public_key:  " << public_key.parms_id() << endl;
cout << "    + secret_key:  " << secret_key.parms_id() << endl;
cout << "    + relin_keys:  " << relin_keys.parms_id() << endl;
cout << "    + galois_keys: " << galois_keys.parms_id() << endl;

Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);

/*
In the BFV scheme plaintexts do not carry a parms_id, but ciphertexts do. Note
how the freshly encrypted ciphertext is at the highest data level.
*/
Plaintext plain("1x^3 + 2x^2 + 3x^1 + 4");
Ciphertext encrypted;
encryptor.encrypt(plain, encrypted);
cout << "    + plain:       " << plain.parms_id() << " (not set in BFV)" << endl;
cout << "    + encrypted:   " << encrypted.parms_id() << endl << endl;

“模切换”是一种改变密文参数的技术。函数Evaluator::mod_switch_to_next 总是切换到链的下一层,而Evaluator::mod_switch_to 切换到与给定parms_id对应的链下的参数集。但是,不可能在链中向上切换。

    print_line(__LINE__);
    cout << "Perform modulus switching on encrypted and print." << endl;
    context_data = context->first_context_data();
    cout << "---->";
    while(context_data->next_context_data())
    {
        cout << " Level (chain index): " << context_data->chain_index() << endl;
        cout << "      parms_id of encrypted: " << encrypted.parms_id() << endl;
        cout << "      Noise budget at this level: "
            << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
        cout << "\\" << endl;
        cout << " \\-->";
        evaluator.mod_switch_to_next_inplace(encrypted);
        context_data = context_data->next_context_data();
    }
    cout << " Level (chain index): " << context_data->chain_index() << endl;
    cout << "      parms_id of encrypted: " << encrypted.parms_id() << endl;
    cout << "      Noise budget at this level: "
        << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
    cout << "\\" << endl;
    cout << " \\-->";
    cout << " End of chain reached" << endl << endl;

在这一点上,我们很难看到这样做的任何好处:我们损失了大量的噪音预算(即计算能力),在每个switch 似乎没有得到任何回报。解密仍能工作。

    print_line(__LINE__);
    cout << "Decrypt still works after modulus switching." << endl;
    decryptor.decrypt(encrypted, plain);
    cout << "    + Decryption of encrypted: " << plain.to_string();
    cout << " ...... Correct." << endl << endl;

然而,有一个隐藏的好处:密文的大小线性取决于系数模量中素数的数量。因此,如果不需要或不打算对给定的密文执行任何进一步的计算,那么在将其发送回密钥持有者进行解密之前,我们不妨将其切换到链中的最小(最后)参数集。

此外,如果我们做得对,我们将看到,噪音预算的损失实际上根本不是问题。

首先,我们重新创建原始密文并执行一些计算。

    cout << "Computation is more efficient with modulus switching." << endl;
    print_line(__LINE__);
    cout << "Compute the fourth power." << endl;
    encryptor.encrypt(plain, encrypted);
    cout << "    + Noise budget before squaring:         "
        << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
    evaluator.square_inplace(encrypted);
    evaluator.relinearize_inplace(encrypted, relin_keys);
    cout << "    + Noise budget after squaring:          "
        << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;

    /*
    Surprisingly, in this case modulus switching has no effect at all on the
    noise budget.
    */
    evaluator.mod_switch_to_next_inplace(encrypted);
    cout << "    + Noise budget after modulus switching: "
        << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;

这意味着在做了足够的计算之后,降低一些系数模量没有任何害处。在某些情况下,人们可能想要稍早地切换到较低的级别,实际上牺牲了过程中的一些噪声预算,以通过较小的参数获得计算性能。我们从打印输出中看到,下一个模数转换应该在噪音预算降至81位左右的理想情况下进行。

    evaluator.square_inplace(encrypted);
    evaluator.relinearize_inplace(encrypted, relin_keys);
    cout << "    + Noise budget after squaring:          "
        << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
    evaluator.mod_switch_to_next_inplace(encrypted);
    cout << "    + Noise budget after modulus switching: "
        << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;

在这一点上,密文仍然可以正确地解密,它的大小非常小,计算是最高效的。注意,解密器可以用于解密密文在模量交换链的任何级别。

    decryptor.decrypt(encrypted, plain);
    cout << "    + Decryption of fourth power (hexadecimal) ...... Correct." << endl;
    cout << "    " << plain.to_string() << endl << endl;

在BFV模量切换是不必要的,在某些情况下,用户可能不希望创建模量切换链,除了最高的两个级别。这可以通过向SEALContext::Create传递一个bool ’ false’来实现。

context = SEALContext::Create(parms, false);

我们可以检查模量交换链是否只针对最高的两个级别(key level和最高数据 level )创建。下面的循环应该只执行一次。

    cout << "Optionally disable modulus switching chain expansion." << endl;
    print_line(__LINE__);
    cout << "Print the modulus switching chain." << endl;
    cout << "---->";
    for (context_data = context->key_context_data(); context_data;
        context_data = context_data->next_context_data())
    {
        cout << " Level (chain index): " << context_data->chain_index() << endl;
        cout << "      parms_id: " << context_data->parms_id() << endl;
        cout << "      coeff_modulus primes: ";
        cout << hex;
        for (const auto &prime : context_data->parms().coeff_modulus())
        {
            cout << prime.value() << " ";
        }
        cout << dec << endl;
        cout << "\\" << endl;
        cout << " \\-->";
    }
    cout << " End of chain reached" << endl << endl;

理解这个例子是非常重要的,因为在CKKS模式中,模切换有一个更基本的目的,除非这些基本性质完全清楚,否则接下来的例子将很难理解。

运行结果:

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值