1. 引言
前序博客 qesa Efficient zero-knowledge arguments in the discrete log setting 学习笔记。
Hoffmann等人 2019年论文 《Efficient zero-knowledge arguments in the discrete log setting 》。
相应的代码实现可参见:
- https://github.com/crate-crypto/qesa (rust)
- https://github.com/emsec/QESA_ZK (C++)
https://github.com/emsec/QESA_ZK
为论文官方实现代码,包含的算法全面,本文主要关注https://github.com/emsec/QESA_ZK
中的代码实现,部分算法会参考https://github.com/crate-crypto/qesa
中的代码实现。
1.1 C++17
https://github.com/emsec/QESA_ZK
代码中采用的是C++2017,需要保证编译服务器的C++版本兼容。
安装升级gcc的方法可参见博客 gcc版本编译安装。
1.2 relic toolkit
https://github.com/relic-toolkit/relic
为一个采用C++实现的密码学库,目前实现的内容有:
- Multiple-precision integer arithmetic
- Prime and Binary field arithmetic
- Elliptic curves over prime and binary fields (NIST curves and pairing-friendly curves)
- Bilinear maps and related extension fields
- Cryptographic protocols (RSA, Rabin, ECDSA, ECMQV, ECSS(Schnorr), ECIES, Sakai-Ohgishi-Kasahara ID-based authenticated key aggrement, Boneh-Lynn-Schacham and Boneh-Boyen short signatures, Paillier and Benaloh homomorphic encryption systems)
https://github.com/emsec/QESA_ZK
是基于relic toolkit 来构建的,需要调用relic库。
注意,QESA_ZK与relic master 版本不兼容,需切换到relic-toolkit-0.5.0
版本再编译,详细方法为:
git clone https://github.com/relic-toolkit/relic
cd relic
git checkout relic-toolkit-0.5.0
mkdir build
cd build
cmake ../ -DWITH="BC;DV;BN;MD;FP;EP" \
-DTESTS=0 -DBENCH=0 -DCHECK=off -DCOLOR=off -DDOCUM=off \
-DFP_PRIME=255 -DFP_PMERS=on -DRAND=UDEV -DARITH=GMP \
-DCOMP="-O3 -funroll-loops -fomit-frame-pointer -fPIC" -DWSIZE=64 -DSTLIB=on -DSHLIB=off $1
make -j
将编译的结果 lib/librelic_s.a
库文件拷贝至 QESA_ZK/proof_system/lib
目录下。
git clone https://github.com/emsec/QESA_ZK
mkdir QESA_ZK/proof_system/lib
cp lib/librelic_s.a QESA_ZK/proof_system/lib/
编译并运行QESA_ZK proof_system:
cd QESA_ZK/proof_system/
make
1.3 基本结构体
struct ProverContext
{
bool first_iteration;
Matrix<G> A;
math::vector<BN> w;
u32 n;
math::vector<G> u_minus_one;
math::vector<G> u_plus_one;
u32 state;
ProverContext() { state = 0; };
};
struct VerifierContext
{
Matrix<G> A;
math::vector<G> t;
u32 n;
bool result;
u32 state;
VerifierContext() { state = 0; result = false; };
};
2. Matrix-vector multiplication argument + Zero-Knowledge
2.1 L M P A s i m p l e Z K LMPA_{simpleZK} LMPAsimpleZK协议
∑
s
t
d
\sum_{std}
∑std协议+
L
M
P
A
n
o
Z
K
LMPA_{noZK}
LMPAnoZK协议=
L
M
P
A
s
i
m
p
l
e
Z
K
LMPA_{simpleZK}
LMPAsimpleZK协议。
// ctx.state = 0 即进行的是
∑
s
t
d
\sum_{std}
∑std操作,来对所有witness进行blinding。
bool test_lmpa()
{
std::cout << "Testing lmpa..." << std::endl;
lmpa::Matrix<G> matrix(4, 12);
for (u32 i = 0; i < matrix.rows(); ++i)
{
for (u32 j = 0; j < matrix.cols(); ++j)
{
matrix(i, j) = G::rand();
}
}
math::vector<BN> witness;
for (u32 j = 0; j < matrix.cols(); ++j)
{
witness.push_back(BN::rand());
}
auto t = matrix * witness;
simple_zk::ProverContext prover_ctx;
simple_zk::VerifierContext verifier_ctx;
simple_zk::begin(prover_ctx, matrix, witness);
simple_zk::begin(verifier_ctx, matrix, t);
std::vector<u8> buffer;
bool continue_prover = true;
bool continue_verifier = true;
while (continue_prover || continue_verifier)
{
if (continue_prover)
{
continue_prover = simple_zk::step_prover(prover_ctx, buffer);
}
if (continue_verifier)
{
continue_verifier = simple_zk::step_verifier(verifier_ctx, buffer);
}
}
if (simple_zk::get_result(verifier_ctx))
{
std::cout << "OK!" << std::endl;
return true;
}
else
{
std::cout << "ERROR!" << std::endl;
}
return false;
}
2.2 L M P A Z K LMPA_{ZK} LMPAZK协议
与
L
M
P
A
s
i
m
p
l
e
Z
K
LMPA_{simpleZK}
LMPAsimpleZK协议 协议不同,不是借助
∑
s
t
d
\sum_{std}
∑std协议来引入随机数
r
⃗
←
F
p
n
\vec{r}\leftarrow\mathbb{F}_p^n
r←Fpn 对整个witness
w
⃗
\vec{w}
w进行blinding。
L
M
P
A
Z
K
LMPA_{ZK}
LMPAZK协议通过引入了随机列来实现blinding,同时相应的随机数来自于masking set,为稀疏的,可减少Prover的计算压力。
详细参见博客 qesa Efficient zero-knowledge arguments in the discrete log setting 学习笔记 第4.2节 zero knowledge of Matrix-vector multiplication argument 内容。
3. zero-knowledge inner product argument
3.1 I P A n o Z K IPA_{noZK} IPAnoZK协议
具体参看 https://github.com/crate-crypto/qesa/blob/master/src/ipa/no_zk.rs
,其中的verify_multiexp
方法 delay all exponentiations到最后一轮,可提升Verifier验证效率。(可参看博文 Bulletproofs: Short Proofs for Confidential Transactions and More学习笔记 第3.1节 “使用Multi-exponentiation对inner-product verification进行优化“。)
fn test_create_nozk_proof() {
let n = 4;
let mut rng = rand::thread_rng();
let a: Vec<Scalar> = (0..n).map(|_| Scalar::random(&mut rng)).collect();
let b: Vec<Scalar> = (0..n).map(|_| Scalar::random(&mut rng)).collect();
let t = inner_product(&a, &b);
let G: Vec<RistrettoPoint> = (0..n).map(|_| RistrettoPoint::random(&mut rng)).collect();
let H: Vec<RistrettoPoint> = (0..n).map(|_| RistrettoPoint::random(&mut rng)).collect();
let Q = RistrettoPoint::hash_from_bytes::<Sha3_512>(b"test point");
let mut prover_transcript = Transcript::new(b"ip_no_zk");
let P = RistrettoPoint::vartime_multiscalar_mul(
a.iter().chain(b.iter()).chain(iter::once(&t)),
G.iter().chain(H.iter()).chain(iter::once(&Q)),
);
// We add the compressed point to the transcript, because we need some non-trivial input to generate alpha
// If this is not done, then the prover always will be able to predict what the first challenge will be
prover_transcript.append_message(b"P", P.compress().as_bytes());
let proof = create(&mut prover_transcript, G.clone(), H.clone(), &Q, a, b);
let mut verifier_transcript = Transcript::new(b"ip_no_zk");
verifier_transcript.append_message(b"P", P.compress().as_bytes());
assert!(proof.verify(&mut verifier_transcript, &G, &H, &Q, n, P, t));
}
3.2 I P A a l m Z K IPA_{almZK} IPAalmZK协议
参见 https://github.com/crate-crypto/qesa/blob/master/src/ipa/alm_zk.rs
,
其中随机向量
r
⃗
′
,
r
⃗
′
′
\vec{r}',\vec{r}''
r′,r′′遵循kenerl guideline来选择,实际生成方式为:
//1. Compute r' and r''
//
let r_prime = sample_gram_schmidt(&b_Vec);
let r_prime_prime = sample_gram_schmidt_twice(&a_Vec, &r_prime);
fn sample_m_n_plus(n: usize) -> Vec<Scalar> {
let mut r: Vec<Scalar> = vec![Scalar::zero(); n];
let mut rng = rand::thread_rng();
r[0] = Scalar::random(&mut rng);
r[1] = Scalar::random(&mut rng);
let mut i = 4;
while i <= n {
r[i / 2] = Scalar::random(&mut rng);
r[i / 2 + 1] = Scalar::random(&mut rng);
i = i * 2
}
r[n - 2] = Scalar::random(&mut rng);
r[n - 1] = Scalar::random(&mut rng);
assert_eq!(n, r.len());
r
}
/// Returns an vector that is orthogonal to `a`
/// Formally, this function projects `b` orthogonally onto the
/// line spanned by `a`
pub fn orth(a: &[Scalar], b: &[Scalar]) -> Vec<Scalar> {
let aa: Scalar = inner_product(&a, &a);
let ab: Scalar = inner_product(&a, &b);
assert_ne!(Scalar::zero(), ab);
let x: Scalar = ab * aa.invert();
let ax: Vec<Scalar> = a.iter().map(|k| k * x).collect();
let b_minus_ax = b.iter().zip(ax.iter()).map(|(r, s)| r - s).collect();
b_minus_ax
}
/// Samples a random matrix and returns a random vector
/// which is orthogonal to `a`
pub fn sample_gram_schmidt(a: &[Scalar]) -> Vec<Scalar> {
let sampled_matrix = sample_m_n_plus(a.len());
orth(a, &sampled_matrix)
}
/// Samples a random matrix and returns a random vector
/// which is orthogonal to `a` and to `b`
/// XXX: We can make a better API for this and naming convention
/// XXX: Naming it sample_gram_schmidt twice could imply that we sample twice
pub fn sample_gram_schmidt_twice(a: &[Scalar], b: &[Scalar]) -> Vec<Scalar> {
let orth_a_b = orth(a, &b);
let orth_a_sample = sample_gram_schmidt(a);
orth(&orth_a_b, &orth_a_sample)
}
I P A a l m Z K IPA_{almZK} IPAalmZK协议 代码为:
fn test_create_almzk_proof() {
let n = 4;
let mut rng = rand::thread_rng();
let a: Vec<Scalar> = (0..n).map(|_| Scalar::random(&mut rng)).collect();
let b: Vec<Scalar> = (0..n).map(|_| Scalar::random(&mut rng)).collect();
let t = inner_product(&a, &b);
let G: Vec<RistrettoPoint> = (0..n).map(|_| RistrettoPoint::random(&mut rng)).collect();
let H: Vec<RistrettoPoint> = (0..n).map(|_| RistrettoPoint::random(&mut rng)).collect();
let mut prover_transcript = Transcript::new(b"ip_alm_zk");
let Q = RistrettoPoint::hash_from_bytes::<Sha3_512>(b"test point");
let C_w = RistrettoPoint::vartime_multiscalar_mul(
a.iter().chain(b.iter()),
G.iter().chain(H.iter()),
);
let proof = super::create(
&mut prover_transcript,
G.clone(),
H.clone(),
&Q,
C_w,
a,
b,
t,
);
let mut verifier_transcript = Transcript::new(b"ip_alm_zk");
assert!(proof.verify(&mut verifier_transcript, &G, &H, &Q, n, C_w, t));
}
3.3 Q E S A I n n e r QESA_{Inner} QESAInner 协议
其中
r
⃗
′
←
F
p
2
\vec{r}'\leftarrow\mathbb{F}_p^2
r′←Fp2,
代码实现参见https://github.com/crate-crypto/qesa/blob/master/src/ipa/qesa_inner.rs
:
#[derive(Clone)]
pub struct Inner {
pub(crate) alm_zk: alm_zk::AlmZK,
pub(crate) c_prime_w: CompressedRistretto,
pub(crate) c_prime_prime_w: CompressedRistretto,
}
fn test_create_qesa_inner_proof() {
let mut rng = rand::thread_rng();
let n = 4;
let (witness, matrix) = helper_create_solutions(n - 2, 2);
let G: Vec<RistrettoPoint> = (0..n).map(|_| RistrettoPoint::random(&mut rng)).collect();
let H: Vec<RistrettoPoint> = (0..n).map(|_| RistrettoPoint::random(&mut rng)).collect();
let Q = RistrettoPoint::hash_from_bytes::<Sha3_512>(b"test point");
let r_prime: Vec<Scalar> = (0..2).map(|_| Scalar::random(&mut rng)).collect();
let mut prover_transcript = Transcript::new(b"qesa_inner");
let proof = create(
&mut prover_transcript,
G.clone(),
H.clone(),
&Q,
&matrix,
witness,
r_prime,
);
let mut verifier_transcript = Transcript::new(b"qesa_inner");
assert!(proof.verify(&mut verifier_transcript, G, H, &Q, &matrix))
}
// Creates a system of quadratic equations with solutions
// and a witness
fn helper_create_solutions(n: usize, num_of_matrices: usize) -> (Vec<Scalar>, BlockMatrix) {
let mut rng = rand::thread_rng();
let mut witness: Vec<Scalar> = (0..n).map(|_| Scalar::random(&mut rng)).collect();
witness[0] = Scalar::one();
let mut bm = BlockMatrix::new();
for _ in 0..num_of_matrices {
let mut gamma_i: Vec<Vec<Scalar>> = Vec::new();
for _ in 0..n {
// Use gram schmidt to create suitable solutions for each system of eqns
let x: Vec<Scalar> = (0..n).map(|_| Scalar::random(&mut rng)).collect();
let row_of_eqns = crate::ipa::gramschmidt::orth(&witness, &x);
gamma_i.push(row_of_eqns)
}
bm.push(gamma_i);
}
// For now, we only use one set of system of equations
(witness, bm)
}
4. QESA协议
4.1 Q E S A Z K QESA_{ZK} QESAZK协议
Q
E
S
A
Z
K
QESA_{ZK}
QESAZK协议 是对
Q
E
S
A
I
n
n
e
r
QESA_{Inner}
QESAInner 协议 的封装。
参见https://github.com/crate-crypto/qesa/blob/master/src/qesa_zk.rs
代码:
// QESA_ZK is a glorified wrapper around Qesa_Inner
pub struct Zk {
inner: qesa_inner::Inner,
}
pub fn create(
transcript: &mut Transcript,
G_Vec: Vec<RistrettoPoint>,
H_Vec: Vec<RistrettoPoint>,
Q: RistrettoPoint,
gamma_i: &BlockMatrix,
w: Vec<Scalar>,
) -> Zk {
// 1. Compute r'
//
let mut rng = rand::thread_rng();
let r_prime = vec![Scalar::random(&mut rng), Scalar::random(&mut rng)];
// The paper states that we should generate C'_w in Qesa_Zk
// Then pass it to Qesa_Inner.
// However, it makes more sense to generate it in Qesa_Inner
let qesa_inner_proof = qesa_inner::create(transcript, G_Vec, H_Vec, &Q, gamma_i, w, r_prime);
Zk {
inner: qesa_inner_proof,
}
}
impl Zk {
pub fn verify(
&self,
transcript: &mut Transcript,
G_Vec: Vec<RistrettoPoint>,
H_Vec: Vec<RistrettoPoint>,
Q: &RistrettoPoint,
gamma_i: &BlockMatrix,
) -> bool {
self.inner.verify(transcript, G_Vec, H_Vec, Q, gamma_i)
}
}
4.2 Q E S A C o p y QESA_{Copy} QESACopy协议
参见https://github.com/emsec/QESA_ZK/blob/master/proof_system/src/test/test_qesa_copy.cpp
代码:
bool test_qesa_copy()
{
std::cout << "testing QESA Copy" << std::endl;
std::vector<qesa::SparseMatrix> matrices;
// setup witness
math::vector<BN> witness = {1};
// setup CRS and commitment CRSs
auto crs = qesa::gen_CRS(1 + 14);
auto com_crs_1 = qesa::copy::get_commitment_crs(crs, {1, 2});
auto com_crs_2 = qesa::copy::get_commitment_crs(crs, {2, 4, 7});
// commitment messages
std::vector<BN> m1 = {BN::rand(), BN::rand()};
BN r1 = BN::rand();
std::vector<BN> m2 = {BN::rand(), BN::rand(), BN::rand()};
BN r2 = BN::rand();
std::vector<BN> m3 = {BN::rand(), BN::rand()};
BN r3 = BN::rand();
// commit to messages
auto com_1 = qesa::copy::commit(crs, com_crs_1, m1, r1);
auto com_2 = qesa::copy::commit(crs, com_crs_2, m2, r2);
auto com_3 = qesa::copy::commit(crs, com_crs_1, m3, r3);
// create qesa::copy mapping
auto mapping = qesa::copy::compute_mapping(crs, 1, {com_crs_1, com_crs_2, com_crs_1});
// execute qesa::copy
qesa::copy::ProverContext pctx(crs);
qesa::copy::VerifierContext vctx(crs);
std::vector<u8> buffer;
buffer.reserve(10000);
bool continue_prover = true;
bool continue_verifier = true;
timer::start();
{
std::vector<std::tuple<std::vector<BN>, BN>> commitments;
commitments.emplace_back(m1, r1);
commitments.emplace_back(m2, r2);
commitments.emplace_back(m3, r3);
qesa::copy::begin(pctx, matrices, mapping, witness, commitments);
}
qesa::copy::begin(vctx, matrices, mapping, {com_1, com_2, com_3});
while (continue_prover || continue_verifier)
{
if (continue_prover)
{
continue_prover = qesa::copy::step_prover(pctx, buffer);
}
if (continue_verifier)
{
continue_verifier = qesa::copy::step_verifier(vctx, buffer);
}
}
auto result = qesa::copy::get_result(vctx);
timer::stop();
std::cout << timer::milliseconds() << "ms, " << std::boolalpha << "result = " << result << std::endl << std::endl;
return result;
}
5. 基于 Q E S A C o p y QESA_{Copy} QESACopy 协议构建range proof
参见https://github.com/emsec/QESA_ZK/blob/master/proof_system/src/test/test_qesa_range_proof.cpp
:
bool test_qesa_range_proof()
{
std::cout << "Testing QESA range proof..." << std::endl;
u32 bitrange = 60;
u32 num_values = 2;
qesa::CRS crs = qesa::gen_CRS(2 + bitrange * num_values);
std::cout << "CRS size: " << crs.n << std::endl;
std::vector<std::tuple<BN, BN>> openings;
std::vector<G> commitments;
for (u32 i = 0; i < num_values; ++i)
{
BN v = BN::rand(bitrange - 1, true);
BN r = BN::rand();
openings.push_back({v, r});
commitments.push_back(qesa::range_proof::commit(crs, v, r));
}
qesa::range_proof::ProverContext pctx(crs);
qesa::range_proof::VerifierContext vctx(crs);
std::vector<u8> buffer;
buffer.reserve(10000);
bool continue_prover = true;
bool continue_verifier = true;
timer::start();
qesa::range_proof::begin(pctx, bitrange, openings);
qesa::range_proof::begin(vctx, bitrange, commitments);
while (continue_prover || continue_verifier)
{
if (continue_prover)
{
continue_prover = qesa::range_proof::step_prover(pctx, buffer);
}
if (continue_verifier)
{
continue_verifier = qesa::range_proof::step_verifier(vctx, buffer);
}
}
auto result = qesa::range_proof::get_result(vctx);
timer::stop();
std::cout << timer::milliseconds() << "ms, " << std::boolalpha << "result = " << result << std::endl << std::endl;
if (!result) return false;
return true;
}
6. 基于 Q E S A C o p y QESA_{Copy} QESACopy 协议构建shuffle argument
参见https://github.com/emsec/QESA_ZK/blob/master/proof_system/src/test/test_shuffle_proof.cpp
:
bool test_shuffle_proof()
{
std::cout << "Testing shuffle proof..." << std::endl;
const u32 shuffle_size = 400;
auto crs = gen_CRS(shuffle_size);
auto keys = el_gamal::keygen();
std::vector<G> plaintexts;
std::vector<el_gamal::Ciphertext> c_old;
std::vector<el_gamal::Ciphertext> c_new;
std::vector<u32> inverse_permutation;
math::vector<BN> rerandomization;
for (u32 i = 0; i < shuffle_size; ++i)
{
auto ptxt = G::rand();
auto ctxt = el_gamal::encrypt(keys.pk, ptxt);
plaintexts.push_back(ptxt);
c_old.push_back(ctxt);
inverse_permutation.push_back(i);
}
std::shuffle(std::begin(inverse_permutation), std::end(inverse_permutation), std::default_random_engine());
std::vector<u32> permutation(shuffle_size, (u32)0);
for (u32 i = 0; i < shuffle_size; ++i)
{
auto rho = BN::rand();
auto ctxt = c_old.at(inverse_permutation.at(i));
ctxt.c_0 += rho * G::get_gen();
ctxt.c_1 += rho * keys.pk;
c_new.push_back(ctxt);
rerandomization.push_back(rho);
permutation[inverse_permutation.at(i)] = i;
}
std::cout << "Begin protocol..." << std::endl;
ProverContext prover_ctx(crs);
VerifierContext verifier_ctx(crs);
begin(prover_ctx, c_old, c_new, keys.pk, permutation, rerandomization);
begin(verifier_ctx, c_old, c_new, keys.pk);
std::vector<u8> buffer;
bool continue_prover = true;
bool continue_verifier = true;
timer::start();
while (continue_prover || continue_verifier)
{
if (continue_prover)
{
continue_prover = step_prover(prover_ctx, buffer);
}
if (continue_verifier)
{
continue_verifier = step_verifier(verifier_ctx, buffer);
}
}
timer::stop();
std::cout << timer::milliseconds() << "ms" << std::endl;
if (get_result(verifier_ctx))
{
std::cout << "OK!" << std::endl;
return true;
}
else
{
std::cout << "ERROR!" << std::endl;
}
return false;
}