[椭圆曲线/可搜索加密] PBC C++ Wrapper/C++封装的PBC库用法简介

C++封装的PBC Library,可以简化代码和避免内存泄露

由Jeza Chen 发表于 June 5, 2020

之前毕业设计的时候用到了PBC Library,在最后性能评估的时候使用Valgrind检测出了一堆的内存泄漏,原因大多是没有调用element_clear()代码释放元素。另一方面,C语言版本的PBC Library用起来其实比较繁琐,需要多次调用element_init_G1()之类的初始化代码,而且元素的运算也不直观(因为C语言不支持运算符重载),用起来很容易犯错误的。

在这个过程中有想过使用PBC C++ Wrapper,但因为代码改动也需要花费不少精力而作罢。现在毕业设计已经搞定了,还是想体验一下C++封装的PBC使用起来到底怎么样。

一句话总结一下后文:C++封装的PBC Library在代码的可读性上有了不少的提升,而且不用手动调用API释放内存,使用PBC C++ Wrapper能避免不少的弯路。

PBC C++ Wrapper的安装

  1. 安装C++ Wrapper之前需要安装好C语言版的PBC Library

  2. 使用git下载源码:git clone git://git-crysp.uwaterloo.ca/pbcwrapper

  3. 进入目录pbcwrapper,在终端输入make编译C++ Wrapper。

  4. 编译后输入./Testing执行测试程序看看C++ Wrapper是否正常工作。

在代码中使用PBC C++ Wrapper

在PBC C++ Wrapper的安装中我们已经使用make将代码编译成了一个静态库libPBC.a,为了能让我们的代码调用这个C++ Wrapper,我们将目录pbcwrapper复制到我们代码所在目录上。

如果我们的项目使用cmake构建,在CMakeLists.txt文件上加入以下内容(需要根据自己的情况更改):

cmake_minimum_required(VERSION 3.15)
project(pbc_test)  # 项目名,根据自己情况更改

# gmp和pbc是必须要链接上去的
link_libraries(gmp)
link_libraries(pbc)

# 引入pbcwrapper的头文件和静态库
include_directories(${PROJECT_SOURCE_DIR}/pbcwrapper)
link_directories(${PROJECT_SOURCE_DIR}/pbcwrapper)

set(CMAKE_CXX_STANDARD 14)

add_executable(pbc_test main.cpp)

# 静态链接,注意pbc_test是前面add_executable指定的,需要根据自己情况修改。
target_link_libraries(pbc_test PBC)

这样我们就可以在自己的代码上使用PBC C++ Wrapper了,我们可以用C++重新实现PBC Library官方文档上的示例程序(即BLS签名算法,算法的流程可以见这一篇博文)。

#include "PBC.h" //包含pbcwrapper的头文件PBC.h

int main() {
    //初始化配对变量e
    char param[1024];
    FILE* file = fopen("a.param", "r");
    size_t count = fread(param, 1, 1024, file);
    fclose(file);
    if (!count) pbc_die("input error");
    Pairing e(param, count);

    G2 g(e);
    Zr secret_key(e);

    G2 public_key = g ^ secret_key; //指数运算
    G1 h(e, (void*)"ABCDEF", 6);
    G1 sig = h ^ secret_key;

    GT temp1 = e(sig, g); //配对运算
    GT temp2 = e(h, public_key);
    if (temp1 == temp2) {
        printf("signature verifies\n");
    } else {
        printf("signature does not verify\n");
    }
}

我们可以使用cmake构建代码看看代码能不能正确执行。如果不想要cmake,可以使用g++命令编译代码:g++ main.cpp -o main -lgmp -lpbc -lPBC -I ./pbcwrapper -L ./pbcwrapper

从上面的代码可以看到,PBC C++ Wrapper有以下几个优点:

  1. 不用调用element_init_G1(h, e)等代码来初始化元素,以及使用element_random(e)随机选取元素赋值给e,直接使用G1 h(e)直接完成了元素的定义、初始化、随机选取操作。

  2. 使用运算符重载等类的机制,使得元素的运算变得直观,比如G2 public_key = g ^ secret_key,在原来的C版本的PBC库需要写成以下的形式:

     element_t public_key;
     element_init_G2(public_key, e);
     element_pow_zn(public_key, g, secret_key);
    

    可见,一行C++代码顶替了三行的C版本代码,使得代码的易读性变强了不少。

  3. 不用手动调用element_clear()释放变量的内存空间。由于所有的元素都是用使用类封装,一旦对象不再使用,析构函数会负责将元素的内存空间给释放掉,不用头疼内存管理的问题。

常用的API

因为PBC C++ Wrapper对PBC Library中许多API都进行封装,所以调用起来特别简单。

配对的定义和初始化

调用Pairing类的构造器构造即可。

分析源码我们可以看到构造函数有以下几个:

Pairing(const char * buf, size_t len);

Pairing(const char * buf);

Pairing(const string &buf);

Pairing(const FILE * buf);

可以看到,配对的初始化不仅支持使用C语言的字符串,也支持C++的字符串std::string,以及FILE指针变量。

元素的定义和初始化

元素初始化前需要完成配对的初始化。

我们以G1元素为例介绍元素的初始化:

G1元素的定义和初始化也是使用G1类的构造函数即可:

G1(const Pairing &e);

G1(const Pairing &e, bool identity);

G1(const Pairing &e, const unsigned char *data, unsigned short len, bool compressed = false, unsigned short base = 0);

G1(const Pairing &e, const void *data, unsigned short len);

G1(const G1 &h, bool identity=false):G(h,identity){}

在C++ Wrapper中,构造器一步就完成了元素的定义、初始化等操作,比PBC Library的调用要简洁的多。在第二个构造函数中,参数identity如果设置为true,则该元素设置为1,即调用了PBC Library的element_set1();如果设置为false,则该元素为随机选取,即相当于调用了element_random()

第三个构造器则类同于C语言版本的element_from_hash()函数。

元素的加减乘除和指数运算

由于使用运算符重载包装了C语言的API,所以我们可以在代码中直接使用运算符+-*/^

配对运算

同样很直观,如果我们将配对对象(即类Pairing的对象)的变量名声明为e,则使用诸如e(g, h)的形式即可进行配对运算,相比C语言版本的pairing_apply()函数要直观得多。

元素的比较

元素的比较直接使用运算符==操作即可,特别方便。

总结

相比C版本的PBC Library,C++包装的版本可读性更强、编程方便,也容易维护。如果追求代码的简洁,建议使用C++版本。