零知识证明实战:libsnark
zk-SNARKs这项技术早在2013年就已经被提出来了。这项技术随即就被MIT和JHU的研究者们拿来尝试解决Bitcoin的隐私保护问题,也就是所谓的Zerocash协议(Zcash的前身)。
Zerocash协议较为复杂,不适合在一篇入门文章中来讲解。因此,本文将以数独游戏为例,介绍如何用zk-SNARKs技术来实现一个有趣的应用。
之所以拿数独为例,是因为知乎上有一篇介绍零知识证明的文章,
它以数独游戏为例,讲了什么是交互式证明,如何将交互式证明转化成非交互式的。本文就算是对该文章的实现吧~
问题描述--数独游戏
在一张桌子上,有n x n张扑克牌,每个扑克牌背后的数字是从1到9,有些扑克牌是翻开的,有些是盖着的。Alice是一个解题高手,她很快就计算出一个可行解。Bob是一个数独爱好者,他想了很久还是解出这个数独谜题。Bob怀疑这个谜题其实根本无解,出题者故意耍大家。于是Alice就打算向Bob证明这个谜题不但有解,而且她知道一个解,但是不打算把这个秘密直接告诉bob。
问题分析
简单分析一下,Alice是想证明她知道
a_{i, j} 是数字1到9;
每一行的数字不重复;
每一列的数字不重复;
9个3x3的子方格中的数字不重复。
应用zk-SNARKs技术实现一个非交互式零知识证明应用的开发顺序大体上是:
- 编写计算的验证逻辑,必须是R1CS的形式;这一步是关键
- 生成证明密钥(proving key)和验证密钥(verification key);
- Alice使用proving key和她的可行解构造证明;
- Bob使用verification key和翻开的扑克牌,验证Alice发过来的证明。
目前有两个工具可以用来将计算转换成R1CS:
- gadgetlib1:这个一个用于手写R1CS的工具包,包含在libsnark项目中。gadgetlib1提供了一些基本运算的R1CS,比如:sha256函数、整数的二进制分解、大小比较等等。除了使用这些工具函数,gadgetlib1还提供了pb_variable、protoboard等类型。
- pb_variable:对应R1CS中变量,记录了变量的下标;
- protoboard:这个对象记录了整个R1CS,包括:一组R1CS约束、每个变量的值,可以判断R1CS方程组的解是否合法。
template<typename FieldT>
class protoboard {
void add_r1cs_constraint(const r1cs_constraint<FieldT> &constr, const std::string &annotation="");
bool is_satisfied() const;
void set_input_sizes(const size_t primary_input_size);
}
- ZoKrates:这个项目定义了一个python-like的语言,并且用Rust实现了从高级语言到R1CS的编译器。提供了几个命令行工具来编译代码、生成证明秘钥与验证密钥、计算R1CS witness、生成证明、验证证明。
./zokrates compile -i 'add.code'
./zokrates setup
./zokrates compute-witness -a 1 2 3
./zokrates generate-proof
./zokrates export-verifier
实现细节 - gadgetlib1-based
验证每个输入来自于 \{1,2, 3, 4\}
/*
* validate Input comes from the set {v1, ..., vn}
* equivalent to constraint (x - v1) ... (x - vn) = 0
*/
template<typename FieldT>
class validateInput_gadget : public gadget<FieldT> {
private:
public:
std::vector<int> values;
pb_variable<FieldT> x;
pb_variable_array<FieldT> intermediates;
validateInput_gadget(protoboard<FieldT> &pb,
const pb_variable<FieldT> &x,
const std::vector<int> &values,
const std::string &annotation_prefix = "validate Input") :
gadget<FieldT>(pb, annotation_prefix), x(x), values(values) {
intermediates.allocate(pb, values.size() - 1, FMT(annotation_prefix, "intermediates"));
}
void generate_r1cs_constraints() {
this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(
{ONE * (-values[0]), x},
{ONE * (-values[1]), x},
{intermediates[0]}));
for (size_t i = 1; i < values.size() - 1; i++) {
this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(
{ONE * (-values[i]), x},
{intermediates[i - 1]},
{intermediates[i]}));
}
}
}
验证每一行、每一列、子网格的输入不重复
/*
* validate that the elements in inputs are different from each other
* which is equivalent to the constraint that \prod (inputs[i] - inputs[j]) * inv = 1
*/
template<typename FieldT>
class checkEquality_gadget : public gadget<FieldT> {
private:
public:
pb_variable<FieldT> inv;
pb_variable_array<FieldT> inputs;
pb_variable_array<FieldT> intermediates;
checkEquality_gadget(protoboard<FieldT> &pb,
const pb_variable_array<FieldT> &inputs,
const std::string &annotation_prefix = "checkEquality") :
gadget<FieldT>(pb, annotation_prefix), inputs(inputs) {
inv.allocate(pb, FMT(annotation_prefix, "inv"));
int num = inputs.size() * (inputs.size() - 1) / 2;
intermediates.allocate(pb, num, FMT(annotation_prefix, "intermediates"));
}
void generate_r1cs_constraints() {
size_t counter = 0;
for (size_t i = 0; i < inputs.size() - 1; i++) {
for (size_t j = i + 1; j < inputs.size(); j++) {
if (i == 0 && j == 1) {
this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(
{ONE},
{inputs[i], inputs[j] * (-1)},
{intermediates[0]}));
counter++;
} else {
this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(
{intermediates[counter - 1]},
{inputs[i], inputs[j] * (-1)},
{intermediates[counter]}
));
counter++;
}
}
}
this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(
{intermediates[intermediates.size() - 1]},
{inv},
{ONE}
));
}
}
总结
借助于最近几年这个领域的发展,实现一个复杂逻辑的非交互式零知识证明协议,已经是很容易的事情。本文希望能够揭开zkp的神秘面纱,真实可用的例子可以让大家对零知识证明更容易理解。如果熟悉整套工具链的开发的话,zkp真的可以构造一些特别有意思的应用。
我已将此样例的代码上传到Github上了。传送门:sudoku-zk-snarks。
PS:以太坊于2017年10月份进行的Byzantinum硬分叉中,已经激活了椭圆曲线点加、倍点、pairing计算功能【EIP196、EIP197】,因此基于这三个precompiled contracts可以实现Pinocchio协议中的证明验证功能。