• Seal库官方示例(三):levels.cpp解析


    这代码主要讲的是modulus switching这个东西,简单来说功能就是用来缩减密文噪声的。这个应该是BGV方案里的,但是官方给的例子却是BFV的,BFV里面没有这个过程,它是把模切换内嵌到了乘法运算里面。代码的最后最后面说了BFV可以不用设置模数链,那么它自带的乘法相关运算应该是自带了模切换。(个人感觉这篇代码应该用BGV来举例更好,BFV效果不明显,代码后面也说了。。。应该是和SEAL库最初是没有BGV代码有关,它是后面添加的)

    当SEALContext被创建的时候,SEAL库将会自动生成一条模交换链,模交换链中的参数和原始参数是一样的,区别在于模数会减小,每一层将会去掉模数链的最后一个模数,直到模数链不可用。每个模数链中的参数集都有自己的唯一索引,可以随时获取,索引值越大,代表它越位于链的前面。

    首先是参数设置

    EncryptionParameters parms(scheme_type::bfv);
    
    size_t poly_modulus_degree = 8192;
    parms.set_poly_modulus_degree(poly_modulus_degree);
    parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 50, 30, 30, 50, 50 }));
    parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));//only for bfv
    
    SEALContext context(parms);
    print_parameters(context);
    cout << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    模数链的顺序很重要,最后一个素数被称为special prime,因为所有的密钥对象都是在这个最高level创建的,而所有数据对象只能在低level。一个非严格的要求就是,special prime应该与coeff_modules中其他质数中最大的一样大。(咱也不知道为啥)
    image.png
    参数输出结果
    image.png

    获取模交换链数据信息的方法
    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

    打印模交换链数据信息,共5层(4-0)
    第一段key level

    print_line(__LINE__);
    cout << "Print the modulus switching chain." << endl;
    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 << " \\-->";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    长这样,主要是参数集的ID和模数
    image.png

    剩下的data level

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    长这样,可以看到模数链在逐层变小,每次去掉最末尾的一个模数。
    image.png

    在创建密钥的时候会调用到key level的参数,下面的代码,创建了密钥并看看是否调用的那些参数

    KeyGenerator keygen(context);
    auto secret_key = keygen.secret_key();
    PublicKey public_key;
    keygen.create_public_key(public_key);
    RelinKeys relin_keys;
    keygen.create_relin_keys(relin_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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对比前面的key level的参数集id,是一样的,说明确实调用了。
    image.png
    BFV的明文没有调用参数集,但是密文调用了

    Encryptor encryptor(context, public_key);
    Evaluator evaluator(context);
    Decryptor decryptor(context, secret_key);
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image.png

    模交换功能就是减小同态运算带来的噪声,其操作是密文运算中将自己所用的参数集切换到下一个level的参数,以进行下一层的计算,这个功能不可逆,也就是不能向上切换,只能向下切换。因为,每一层都少了一个模数,假定上一个是 Q Q Q,下一层是 Q ′ Q' Q,那么模切换的操作实质就是密文乘以了 Q ′ / Q Q'/Q Q/Q也就是 1 / q i 1/q_i 1/qi,对应噪声也乘以了这么多,就达到了减小噪声的目的。库中提供的函数如下,
    Evaluator::mod_switch_to_next:切换到下一层
    Evaluator::mod_switch_to:可以切换参数id进行交换
    下面的代码展示了每一层data level的模数切换情况

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    结果如下,可以看到每一层的ID都是对应了上面data level的参数id,每一层的噪声预算也在减少
    image.png
    看起来啥也没做,单单是切换一下层数,噪声预算就被消耗了?前面的文章说了,多项式系数模越大,计算能力就越强,计算能力的体现就在噪声运算上。这里的模减小了。
    噪声预算不为0,解密正确

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image.png

    未使用模切换,计算密文的2次幂,4次幂。

    cout << "Computation is more efficient with modulus switching." << endl;
    print_line(__LINE__);
    cout << "Compute the 8th power." << endl;
    encryptor.encrypt(plain, encrypted);
    cout << "    + Noise budget fresh:                   " << decryptor.invariant_noise_budget(encrypted) << " bits"
         << endl;
    evaluator.square_inplace(encrypted);
    evaluator.relinearize_inplace(encrypted, relin_keys);
    cout << "    + Noise budget of the 2nd power:         " << decryptor.invariant_noise_budget(encrypted) << " bits"
         << endl;
    evaluator.square_inplace(encrypted);
    evaluator.relinearize_inplace(encrypted, relin_keys);
    cout << "    + Noise budget of the 4th power:         " << decryptor.invariant_noise_budget(encrypted) << " bits"
         << endl;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    使用了模切换后计算第8次幂

    evaluator.mod_switch_to_next_inplace(encrypted);
    cout << "    + Noise budget after modulus switching:  " << decryptor.invariant_noise_budget(encrypted) << " bits"
         << endl;
    evaluator.square_inplace(encrypted);
    evaluator.relinearize_inplace(encrypted, relin_keys);
    cout << "    + Noise budget of the 8th power:         " << 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 the 8th power (hexadecimal) ...... Correct." << endl;
    cout << "    " << plain.to_string() << endl << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果如下,
    image.png
    可以看到BFV中应用模交换没有什么影响,这意味着,在做了足够的计算后,降低一些系数根本没有坏处。在某些情况下,也就是密文计算不需要更多的噪声运算时,我们可以稍微提前切换到较低的级别以加快计算。解密是可以在任意层进行解密的,层数越低,模数越低,计算速度就越快。

    另外,BFV中,modulus switching不是必须的,在BFV方案里把这个操作近似的放进了乘法运算中,那么也就不需要太长的模数切换链,可以通过设置改变,使其只生成前两层,也就是key level和highest data level(所以我说其实应该拿BGV来举例的)

    context = SEALContext(parms, false);
    
    • 1

    打印一下看看

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这就没有生成后续的链。
    image.png
    BGV里是需要的,ckks里面用的是重缩放,但原理基本类似。

  • 相关阅读:
    Kubernetes:kube-apiserver 之启动流程(二)
    深眸科技自研工业AI视觉检测设备,检测精度99.9%加速智造进程
    剑指Offer II 031. 最近最少使用缓存(LRU)
    一致性哈希算法
    软件工程——期末复习知识点汇总
    配置d3dx9.h
    Springboot开启Quartz集群
    瑞士奢侈手表制造商百年灵现在接受比特币
    ChatGPT高效提问——角色提示
    MFC C++ BMP图片向右旋转90度示例函数 WCHAR与CHAR互转 CStringW CStringA互转
  • 原文地址:https://blog.csdn.net/qq_43271194/article/details/128124966