R1CS:即rank-1 constraint system,可将其理解为一个方程组。
Prover需要向Verifier证明其知道满足该方程组所有方程式的解,证明过程可转化为Prover知道3组向量 ( a ⃗ , b ⃗ , c ⃗ ) (\vec{a},\vec{b},\vec{c}) (a,b,c)以及1组对应于R1CS解的向量 s ⃗ \vec{s} s,使得 < s ⃗ , a ⃗ > ∗ < s ⃗ , b ⃗ > − < s ⃗ , c ⃗ > = 0 <\vec{s},\vec{a}>*<\vec{s},\vec{b}>-<\vec{s},\vec{c}>=0 <s,a>∗<s,b>−<s,c>=0成立。
其中 < s ⃗ , a ⃗ > = ∑ i = 1 n s i a i <\vec{s},\vec{a}>=\sum_{i=1}^{n}s_ia_i <s,a>=∑i=1nsiai为dot product。
Vitalik 博客Quadratic Arithmetic Programs: from Zero to Hero中主要是举例证明知道 x 3 + x + 5 = = 35 x^3+x+5==35 x3+x+5==35的答案 x = 3 x=3 x=3,将该方程式的证明拆分为如下4组线性等式:
s y m _ 1 = x ∗ x sym\_1=x*x sym_1=x∗x
y = s y m _ 1 ∗ x y=sym\_1*x y=sym_1∗x
s y m _ 2 = y + x sym\_2=y+x sym_2=y+x
∼ o u t = s y m _ 2 + 5 \sim out=sym\_2+5 ∼out=sym_2+5
左侧的变量组成为 [ s y m _ 1 , y , s y m _ 2 , ∼ o u t ] [sym\_1,y,sym\_2,\sim out] [sym_1,y,sym_2,∼out],在此基础上增加1和x变量,组成的向量 s ⃗ = [ ∼ o n e , x , s y m _ 1 , y , s y m _ 2 , ∼ o u t ] \vec{s}=[\sim one,x,sym\_1,y,sym\_2,\sim out] s=[∼one,x,sym_1,y,sym_2,∼out]。为了跟Vitalik的博客对应,调整顺序为 s ⃗ = [ ∼ o n e , x , ∼ o u t , s y m _ 1 , y , s y m _ 2 ] \vec{s}=[\sim one,x,\sim out,sym\_1,y,sym\_2] s=[∼one,x,∼out,sym_1,y,sym_2]。
若Prover知道满足上述方程式组的解,则对应的 s ⃗ = [ ∼ o n e , x , ∼ o u t , s y m _ 1 , y , s y m _ 2 ] = [ 1 , 3 , 35 , 9 , 27 , 30 ] \vec{s}=[\sim one,x,\sim out,sym\_1,y,sym\_2]=[1,3,35,9,27,30] s=[∼one,x,∼out,sym_1,y,sym_2]=[1,3,35,9,27,30]。
1)对应第一个方程式: s y m _ 1 = x ∗ x sym\_1=x*x sym_1=x∗x,满足 < s ⃗ , a ⃗ > ∗ < s ⃗ , b ⃗ > − < s ⃗ , c ⃗ > = 0 <\vec{s},\vec{a}>*<\vec{s},\vec{b}>-<\vec{s},\vec{c}>=0 <s,a>∗<s,b>−<s,c>=0成立的 ( a ⃗ , b ⃗ , c ⃗ ) (\vec{a},\vec{b},\vec{c}) (a,b,c)为:
a ⃗ = [ 0 , 1 , 0 , 0 , 0 , 0 ] \vec{a}=[0,1,0,0,0,0] a=[0,1,0,0,0,0]
b ⃗ = [ 0 , 1 , 0 , 0 , 0 , 0 ] \vec{b}=[0,1,0,0,0,0] b=[0,1,0,0,0,0]
c ⃗ = [ 0 , 0 , 0 , 1 , 0 , 0 ] \vec{c}=[0,0,0,1,0,0] c=[0,0,0,1,0,0]
2)对应第二个方程式: y = s y m _ 1 ∗ x y=sym\_1*x y=sym_1∗x,对应的 ( a ⃗ , b ⃗ , c ⃗ ) (\vec{a},\vec{b},\vec{c}) (a,b,c)为:
a ⃗ = [ 0 , 0 , 0 , 1 , 0 , 0 ] \vec{a}=[0,0,0,1,0,0] a=[0,0,0,1,0,0]
b ⃗ = [ 0 , 1 , 0 , 0 , 0 , 0 ] \vec{b}=[0,1,0,0,0,0] b=[0,1,0,0,0,0]
c ⃗ = [ 0 , 0 , 0 , 0 , 1 , 0 ] \vec{c}=[0,0,0,0,1,0] c=[0,0,0,0,1,0]
3)对应第三个方程式: s y m _ 2 = y + x sym\_2=y+x sym_2=y+x,对应的 ( a ⃗ , b ⃗ , c ⃗ ) (\vec{a},\vec{b},\vec{c}) (a,b,c)为:
a ⃗ = [ 0 , 1 , 0 , 0 , 1 , 0 ] \vec{a}=[0,1,0,0,1,0] a=[0,1,0,0,1,0]
b ⃗ = [ 1 , 0 , 0 , 0 , 0 , 0 ] \vec{b}=[1,0,0,0,0,0] b=[1,0,0,0,0,0]
c ⃗ = [ 0 , 0 , 0 , 0 , 0 , 1 ] \vec{c}=[0,0,0,0,0,1] c=[0,0,0,0,0,1]
4)对应第四个方程式: ∼ o u t = s y m _ 2 + 5 \sim out=sym\_2+5 ∼out=sym_2+5,对应的 ( a ⃗ , b ⃗ , c ⃗ ) (\vec{a},\vec{b},\vec{c}) (a,b,c)为:
a ⃗ = [ 0 , 1 , 0 , 0 , 1 , 0 ] \vec{a}=[0,1,0,0,1,0] a=[0,1,0,0,1,0]
b ⃗ = [ 1 , 0 , 0 , 0 , 0 , 0 ] \vec{b}=[1,0,0,0,0,0] b=[1,0,0,0,0,0]
c ⃗ = [ 0 , 0 , 0 , 0 , 0 , 1 ] \vec{c}=[0,0,0,0,0,1] c=[0,0,0,0,0,1]
将所有方程式的 ( a ⃗ , b ⃗ , c ⃗ ) (\vec{a},\vec{b},\vec{c}) (a,b,c)向量分别组成 ( A , B , C ) (A,B,C) (A,B,C)三个矩阵,对应的为:
A = [ 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 5 0 0 0 0 1 ] A=\begin{bmatrix} 0& 1 & 0 & 0 & 0 & 0\\ 0& 0 & 0 & 1 & 0 & 0\\ 0& 1 & 0 & 0 & 0 & 1\\ 5& 0 & 0 & 0 & 0 & 1 \end{bmatrix} A=⎣⎢⎢⎡000510100000010000000011⎦⎥⎥⎤
B = [ 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ] B=\begin{bmatrix} 0& 1 & 0 & 0 & 0 & 0\\ 0& 1 & 0 & 0 & 0 & 0\\ 1& 0 & 0 & 0 & 0 & 0\\ 1& 0 & 0 & 0 & 0 & 0 \end{bmatrix} B=⎣⎢⎢⎡001111000000000000000000⎦⎥⎥⎤
C = [ 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 0 ] C=\begin{bmatrix} 0& 0 & 0 & 1 & 0 & 0\\ 0& 0 & 0 & 0 & 1 & 0\\ 0& 0 & 0 & 0 & 0 & 1\\ 0& 0 & 1 & 0 & 0 & 0 \end{bmatrix} C=⎣⎢⎢⎡000000000001100001000010⎦⎥⎥⎤
s ⃗ \vec{s} s中的 ∼ o n e / x / ∼ o u t \sim one/x/\sim out ∼one/x/∼out对应:
const ONE: Variable; // ~one
base_var // x
result_var // ~out
pub enum Variable { // 对应的是每个乘法门的两个输入A和B,一个输出C。C=A*B。
A(usize),
B(usize),
C(usize),
}
pub struct LinearCombination(Vec<(Variable, Coeff)>); //线下方程式由系数和参数组成
pub enum Num { //数值分常量数值和变量数值
Constant(Coeff),
Allocated(Coeff, AllocatedNum),
}
impl From<(Coeff, Num)> for Num {
fn from(num: (Coeff, Num)) -> Self {
match num.1 {
Num::Constant(coeff) => Num::Constant(num.0 * coeff),
Num::Allocated(coeff, n) => Num::Allocated(num.0 * coeff, n),
}
}
}
pub struct AllocatedNum {
value: Option,
var: Variable,
}
pub enum Coeff {
Zero,
One,
NegativeOne,
Full(F),
}
pub fn value(&self) -> F {
match *self {
Coeff::Zero => F::zero(),
Coeff::One => F::one(),
Coeff::NegativeOne => -F::one(),
Coeff::Full(val) => val,
}
}
pub struct Combination {
value: Option,
terms: Vec>,
}
Halo中存在两种类型的circuit:
pub trait RecursiveCircuit {
fn base_payload(&self) -> Vec;
fn synthesize>(
&self,
cs: &mut CS,
old_payload: &[AllocatedBit],
new_payload: &[AllocatedBit],
) -> Result<(), SynthesisError>;
}
pub trait Circuit {
fn synthesize>(&self, cs: &mut CS) -> Result<(), SynthesisError>;
}
/// This is a "namespaced" constraint system which borrows a constraint system (pushing
/// a namespace context) and, when dropped, pops out of the namespace context.
pub struct Namespace<'a, FF: Field, CS: ConstraintSystem + 'a>(&'a mut CS, PhantomData);
Halo中对Backend
trait做了三次实现,分别是:prove(proof()
)、verify(verify()
)和devloping circuit(is_satisfisfied()
)时。
/// This is a backend for the `SynthesisDriver` to relay information about
/// the concrete circuit. One backend might just collect basic information
/// about the circuit for verification, while another actually constructs
/// a witness.
pub trait Backend {
type LinearConstraintIndex;
/// Get the value of a variable. Can return None if we don't know.
fn get_var(&self, _var: Variable) -> Option {
None
}
/// Set the value of a variable.
///
/// `allocation` will be Some if this multiplication gate is being used for
/// variable allocation, and None if it is being used as a constraint.
///
/// Might error if this backend expects to know it.
fn set_var(
&mut self,
_annotation: Option,
_var: Variable,
_value: F,
) -> Result<(), SynthesisError>
where
F: FnOnce() -> Result,
A: FnOnce() -> AR,
AR: Into,
{
Ok(())
}
/// Create a new multiplication gate.
///
/// `allocation` will be Some if this multiplication gate is being used as a
/// constraint, and None if it is being used for variable allocation.
fn new_multiplication_gate(&mut self, _annotation: Option)
where
A: FnOnce() -> AR,
AR: Into,
{
}
/// Create a new linear constraint, returning a cached index.
fn new_linear_constraint(&mut self, annotation: A) -> Self::LinearConstraintIndex
where
A: FnOnce() -> AR,
AR: Into;
/// Insert a term into a linear constraint.
fn insert_coefficient(
&mut self,
_var: Variable,
_coeff: Coeff,
_y: &Self::LinearConstraintIndex,
) {
}
/// Compute a `LinearConstraintIndex` from `q`.
fn get_for_q(&self, q: usize) -> Self::LinearConstraintIndex;
/// Mark y^{_index} as the power of y cooresponding to the public input
/// coefficient for the next public input, in the k(Y) polynomial. Also
/// gives the value of the public input.
fn new_k_power(&mut self, _index: usize, _value: Option) -> Result<(), SynthesisError> {
Ok(())
}
/// Create a new (sub)namespace and enter into it.
fn push_namespace(&mut self, _name_fn: N)
where
NR: Into,
N: FnOnce() -> NR,
{
}
/// Exit out of the existing namespace.
fn pop_namespace(&mut self, _gadget_name: Option) {}
}
struct Synthesizer> {
backend: B,
current_variable: Option,
_marker: PhantomData,
//对应的即为论文《Efficient Zero-Knowledge Arguments for Arithmetic Circuits in the Discrete Log Setting》P24页中的Q linear constraints on the wires......
q: usize, //q为Linear constraints的数量,k(Y)=\sum_{q=1}^{Q}k_q*Y^q。
n: usize, //n为乘法门的序号。每个乘法门有两个输入,分别是a和b,有一个输出c。c=a*b。
}
异或操作a^b=c
,对应的R1CS表示为:(a + a) * (b) = (a + b - c)
,详细的推理过程如下:
// ¬(a ∧ b) ∧ ¬(¬a ∧ ¬b) = c
// (1 - (a * b)) * (1 - ((1 - a) * (1 - b))) = c
// (1 - ab) * (1 - (1 - a - b + ab)) = c
// (1 - ab) * (a + b - ab) = c
// a + b - ab - (a^2)b - (b^2)a + (a^2)(b^2) = c
// a + b - ab - ab - ab + ab = c
// a + b - 2ab = c
// -2a * b = c - a - b
// 2a * b = a + b - c
//
// d * e = f
// d = 2a
// e = b
// f = a + b - c
ConstraintSystem由大量LinearCombination组成。
/// Constrain (x)^5 = (x^5), and return variables for x and (x^5).
///
/// We can do so with three multiplication constraints and five linear constraints:
///
/// a * b = c
/// a := x
/// b = a
/// c := x^2
///
/// d * e = f
/// d = c
/// e = c
/// f := x^4
///
/// g * h = i
/// g = f
/// h = x
/// i := x^5
fn constrain_pow_five(
mut cs: CS,
x: Option,
) -> Result<(Variable, Variable), SynthesisError>
where
F: Field,
CS: ConstraintSystem,
{
let x2 = x.and_then(|x| Some(x.square()));
let x4 = x2.and_then(|x2| Some(x2.square()));
let x5 = x4.and_then(|x4| x.and_then(|x| Some(x4 * x)));
let (base_var, b_var, c_var) = cs.multiply(
|| "x^2",
|| {
let x = x.ok_or(SynthesisError::AssignmentMissing)?;
let x2 = x2.ok_or(SynthesisError::AssignmentMissing)?;
Ok((x, x, x2))
},
)?;
cs.enforce_zero(LinearCombination::from(base_var) - b_var);
let (d_var, e_var, f_var) = cs.multiply(
|| "x^4",
|| {
let x2 = x2.ok_or(SynthesisError::AssignmentMissing)?;
let x4 = x4.ok_or(SynthesisError::AssignmentMissing)?;
Ok((x2, x2, x4))
},
)?;
cs.enforce_zero(LinearCombination::from(c_var) - d_var);
cs.enforce_zero(LinearCombination::from(c_var) - e_var);
let (g_var, h_var, result_var) = cs.multiply(
|| "x^5",
|| {
let x = x.ok_or(SynthesisError::AssignmentMissing)?;
let x4 = x4.ok_or(SynthesisError::AssignmentMissing)?;
let x5 = x5.ok_or(SynthesisError::AssignmentMissing)?;
Ok((x4, x, x5))
},
)?;
cs.enforce_zero(LinearCombination::from(f_var) - g_var);
cs.enforce_zero(LinearCombination::from(base_var) - h_var);
Ok((base_var, result_var))
}
参考资料:
[1] Vitalik 博客Quadratic Arithmetic Programs: from Zero to Hero