zkp 为使用merlin 的Schnorr零知识证明工具包,基于ristretto group做的实例化。【ristretto为对cofactor>1曲线的抽象,ristretto对外表现为将non-prime-order的曲线抽象为a prime-order group。具体的细节可参见博客1ristretto对cofactor>1的椭圆曲线(如Curve25519等)的兼容(含Curve25519 cofactor的sage验证)、博客2ristretto255 point压缩和解压缩算法(1)——affine坐标系下、博客3ristretto255 point压缩和解压缩算法(2)——extended坐标系下】
在Merlin中,一个transcript即为a public-coin argument,通过Transcript::new()
函数来创建。Prover的消息通过append_message()
(老的版本采用的是commit_bytes()
)函数来添加到transcript中。Verifier的challenges则通过challenge_bytes()
函数来计算。
Merlin的transcript应由协议的API消费者创建,而不能由protocol内部创建。
Merlin提供的接口是面向字节的byte-oriented,而实际证明协议中,需要的是特定格式的数据,如接收challenge scalars,而不是challenge bytes。这个转化,可通过博客Merlin——零知识证明(2)应用篇中的transcript protocol来实现。不同的协议,可以自定义实现自己的trait TranscriptProtocol
。
在4核8G Ubuntu16.04系统上运行。
cargo +nightly bench
running 8 tests
test batch_verify_batchable_dleq_1 ... bench: 198,877 ns/iter (+/- 27,535)
test batch_verify_batchable_dleq_16 ... bench: 1,452,702 ns/iter (+/- 208,581)
test batch_verify_batchable_dleq_4 ... bench: 450,939 ns/iter (+/- 45,632)
test batch_verify_batchable_dleq_64 ... bench: 5,339,812 ns/iter (+/- 273,354)
test create_batchable_dleq ... bench: 221,563 ns/iter (+/- 21,566)
test create_compact_dleq ... bench: 221,027 ns/iter (+/- 14,245)
test verify_batchable_dleq ... bench: 183,422 ns/iter (+/- 11,620)
test verify_compact_dleq ... bench: 210,587 ns/iter (+/- 12,661)
由此可知,batch_verifier.rs
中的verify_batchable()
函数verify proof的效率要由于verifier.rs
中的实现。
sig_and_vrf_example.rs
中sign/verify
验证的是Schnorr签名的流程,最关键的是这段代码:(可参考博客Schnorr signature (Schnorr 签名)数学原理、
elliptic-curve签名验证verify signature in EdDSA)
注意,由于使用的是Merlin transcript,变量的赋值顺序会影响transcript state,所以Prover和Verifier应严格按照相同的次序【将interactive变为non-interactive】来进行Merlin transcript的更新,否则会出现verify失败的情况。
/// This proof has `m+1` 32-byte elements, where `m` is the number of
/// secret variables. This means there is no space savings for a
/// `CompactProof` over a `BatchableProof` when there is only one
/// statement.
#[derive(Clone, Serialize, Deserialize)]
pub struct CompactProof {
/// The Fiat-Shamir challenge.
pub challenge: Scalar,
/// The prover's responses, one per secret variable.
pub responses: Vec,
}
/// A Schnorr proof in batchable format.
///
/// This proof has `m+n` 32-byte elements, where `m` is the number of
/// secret variables and `n` is the number of statements.
#[derive(Clone, Serialize, Deserialize)]
pub struct BatchableProof {
/// Commitments to the prover's blinding factors.
pub commitments: Vec,
/// The prover's responses, one per secret variable.
pub responses: Vec,
}
#[macro_export]
macro_rules! define_proof {
(
$proof_module_name:ident // Name of the module to create
,
$proof_label_string:expr // A string literal, used as a domain separator
,
( $($secret_var:ident),+ ) // Secret variables, sep by commas
,
( $($instance_var:ident),* ) // Public instance variables, separated by commas
,
( $($common_var:ident),* ) // Public common variables, separated by commas
:
// List of statements to prove
// Format: LHS = ( ... RHS expr ... ),
$($lhs:ident = $statement:tt),+
) => {......}
/// Named parameters for [`prove_compact`] and [`prove_batchable`].
#[derive(Copy, Clone)]
pub struct ProveAssignments<'a> {
$(pub $secret_var: &'a Scalar,)+
$(pub $instance_var: &'a RistrettoPoint,)+
$(pub $common_var: &'a RistrettoPoint,)+
}
/// Named parameters for [`verify_compact`] and [`verify_batchable`].
#[derive(Copy, Clone)]
pub struct VerifyAssignments<'a> {
$(pub $instance_var: &'a CompressedRistretto,)+
$(pub $common_var: &'a CompressedRistretto,)+
}
/// Given a transcript and assignments to secret and public variables, produce a proof in batchable format.
pub fn prove_batchable(
transcript: &mut Transcript,
assignments: ProveAssignments,
) -> (BatchableProof, CompressedPoints) {
let (prover, compressed) = build_prover(transcript, assignments);
(prover.prove_batchable(), compressed)
}
fn build_prover<'a>(
transcript: &'a mut Transcript,
assignments: ProveAssignments,
) -> (Prover<'a>, CompressedPoints) {
use self::internal::*;
use $crate::toolbox::prover::*;
let mut prover = Prover::new(PROOF_LABEL.as_bytes(), transcript);
let secret_vars = SecretVars {
$(
$secret_var: prover.allocate_scalar(
TRANSCRIPT_LABELS.$secret_var.as_bytes(),
*assignments.$secret_var,
),
)+
};
struct VarPointPairs {
$( pub $instance_var: (PointVar, CompressedRistretto), )+
$( pub $common_var: (PointVar, CompressedRistretto), )+
}
let pairs = VarPointPairs {
$(
$instance_var: prover.allocate_point(
TRANSCRIPT_LABELS.$instance_var.as_bytes(),
*assignments.$instance_var,
),
)+
$(
$common_var: prover.allocate_point(
TRANSCRIPT_LABELS.$common_var.as_bytes(),
*assignments.$common_var,
),
)+
};
// XXX return compressed points
let public_vars = PublicVars {
$($instance_var: pairs.$instance_var.0,)+
$($common_var: pairs.$common_var.0,)+
};
let compressed = CompressedPoints {
$($instance_var: pairs.$instance_var.1,)+
$($common_var: pairs.$common_var.1,)+
};
proof_statement(&mut prover, secret_vars, public_vars);
(prover, compressed)
}
/// The transcript labels used for each secret variable.
pub const TRANSCRIPT_LABELS: TranscriptLabels = TranscriptLabels {
$( $secret_var: stringify!($secret_var), )+
$( $instance_var: stringify!($instance_var), )+
$( $common_var: stringify!($common_var), )+
};
/// The underlying proof statement generated by the macro invocation.
///
/// This function exists separately from the proving
/// and verification functions to allow composition of
/// different proof statements with common variable
/// assignments.
pub fn proof_statement(
cs: &mut CS,
secrets: SecretVars,
publics: PublicVars,
) {
$(
cs.constrain(
publics.$lhs,
__compute_formula_constraint!( (publics, secrets) $statement ),
);
)+
}
pub struct Prover<'a> {
transcript: &'a mut Transcript,
scalars: Vec,
points: Vec,
point_labels: Vec<&'static [u8]>,
constraints: Vec<(PointVar, Vec<(ScalarVar, PointVar)>)>,
}
/// A secret variable used during proving.
#[derive(Copy, Clone)]
pub struct ScalarVar(usize);
/// A public variable used during proving.
#[derive(Copy, Clone)]
pub struct PointVar(usize);
/// Allocate and assign a secret variable with the given `label`.
pub fn allocate_scalar(&mut self, label: &'static [u8], assignment: Scalar) -> ScalarVar {
self.transcript.append_scalar_var(label);
self.scalars.push(assignment);
ScalarVar(self.scalars.len() - 1)
}
/// The compact and batchable proofs differ only by which data they store.
fn prove_impl(self) -> (Scalar, Vec, Vec) {
// Construct a TranscriptRng
let mut rng_builder = self.transcript.build_rng();
for scalar in &self.scalars {
rng_builder = rng_builder.commit_witness_bytes(b"", scalar.as_bytes());
}
let mut transcript_rng = rng_builder.finalize(&mut thread_rng());
// Generate a blinding factor for each secret variable
let blindings = self
.scalars
.iter()
.map(|_| Scalar::random(&mut transcript_rng))
.collect::>();
// Commit to each blinded LHS
let mut commitments = Vec::with_capacity(self.constraints.len());
for (lhs_var, rhs_lc) in &self.constraints {
let commitment = RistrettoPoint::multiscalar_mul(
rhs_lc.iter().map(|(sc_var, _pt_var)| blindings[sc_var.0]),
rhs_lc.iter().map(|(_sc_var, pt_var)| self.points[pt_var.0]),
);
let encoding = self
.transcript
.append_blinding_commitment(self.point_labels[lhs_var.0], &commitment);
commitments.push(encoding);
}
// Obtain a scalar challenge and compute responses
let challenge = self.transcript.get_challenge(b"chal");
let responses = Iterator::zip(self.scalars.iter(), blindings.iter())
.map(|(s, b)| s * challenge + b)
.collect::>();
(challenge, responses, commitments)
}
/// Consume this prover to produce a compact proof.
pub fn prove_compact(self) -> CompactProof {
let (challenge, responses, _) = self.prove_impl();
CompactProof {
challenge,
responses,
}
}
/// Consume this prover to produce a batchable proof.
pub fn prove_batchable(self) -> BatchableProof {
let (_, responses, commitments) = self.prove_impl();
BatchableProof {
commitments,
responses,
}
}
参考资料:
[1] zkp
[2] merlin
[3] Schnorr signature (Schnorr 签名)数学原理
[4] Merlin——零知识证明(2)应用篇
[5] https://medium.com/@hdevalence/merlin-flexible-composable-transcripts-for-zero-knowledge-proofs-28d9fda22d9a