[PLONK 1.] PLONK-style Arithmetization

DoHoon Kim·2022년 7월 19일
5

PLONK

목록 보기
1/5

이번 시리즈는 zk-SNARK를 발전시킨 개념인 PLONK에 대해 알아보도록 하겠습니다. PLONK-style arithmetization과 polynomial commitment의 종류 중 하나인 KZG commitment, 그리고 Plookup에 대해서도 다룰 예정입니다.

이번 글에서는 PLONK-style arithmetization에 대해서 알아보겠습니다. 이 글은 Vitalik ButerinUnderstanding PLONK 를 기반으로 작성됐습니다.

Why PLONK?

PLONK-style arithmetization을 설명하기에 앞서, PLONK라는 개념이 왜 나왔는지에 대해 (개인적인 해석이 담긴) 설명하겠습니다.

앞서, zk-SNARK에서는 증명하고자 하는 computational problem을 arithmetic circuit으로 표현한 후, R1CS를 circuit에서 추출했습니다. 그리고 R1CS를 compact하게 표현하기 위해 QAP라는 형태로 변환시켰습니다. 이후, Verifiable Blind Evaluation 을 이용해 Prover는 Verifier에게 QAP를 만족하는 다항식을 알고 있음을 증명할 수 있었습니다. 그리고 증명 과정이 non-interactive하게 작동할 수 있도록 trusted setup 과정이 도입됐습니다.

기존의 SNARK에서 R1CS를 이용해 arithmetic circuit의 constraint를 표현했는데, R1CS의 벡터의 크기가 circuit에 존재하는 모든 변수의 개수와 같아야 했습니다. R1CS의 solution vector가 circuit을 consistent하게 만들어야 하기 때문입니다. 이는 circuit의 크기가 커질수록 R1CS 벡터들도 비대해진다는 문제가 있습니다. 이를 해결하기 위해 PLONK에서는 gate 간의 wire의 값들을 consistent하게 만들어 주는 constraint를 분리합니다.(개인적인 해석이니, 잘못 이해한 것이라면 댓글로 코멘트 남겨주세요!) 따라서 arithmetic circuit을 QAP가 아닌 새로운 형태로 변환하게 되는데, 이를 PLONK-style arithmetization이라고 합니다.

그리고 기존의 SNARK에서는 QAP 별로 별도의 trusted setup 과정이 필요하다는 단점이 존재했습니다. PLONK에서는 universal하고 updateable한 trusted setup을 도입하기 위해 polynomial commitment라는 새로운 scheme을 사용합니다. Polynomial commitment는 이후 글에서 다루도록 하겠습니다.

PLONK-style arithmetization

이제 PLONK에서는 어떻게 arithmetic circuit을 변환하는지 살펴보겠습니다. Prover가 x^3 + x + 5 = 35를 만족하는 x 값을 알고 있음을 증명하려 합니다. 이 문제는 다음과 같은 arithmetic circuit으로 표현될 수 있습니다.

Prover는 위 arithmetic circuit을 만족하는 wire의 값들을 알고 있음을 증명해야 하는데, 이를 가리켜 witness라고 명명합니다.

PLONK에는 Prover의 witness가 arithmetic circuit을 만족하는지 검사하기 위해 두 가지 constraint이 존재합니다. 각 gate에서 wire의 값들이 만족해야 하는 gate constraint와, circuit이 consistent해지도록 wire들 간에 성립해야 하는 copy constraint가 있습니다.

Gate constraint

먼저 gate constraint를 알아보겠습니다. ii번째 gate의 left, right, output wire들을 각각 ai,bi,cia_i, b_i, c_i라고 할 때 gate constraint는 다음과 표현됩니다.

(QLi)ai+(QRi)bi+(QOi)ci+(QMi)aibi+QCi=0(Q_{L_i})a_i + (Q_{R_i})b_i + (Q_{O_i})c_i + (Q_{M_i})a_ib_i + Q_{C_i} = 0

위 식에서 QLi,QRi,QOi,QMi,QCiQ_{L_i}, Q_{R_i}, Q_{O_i}, Q_{M_i}, Q_{C_i}는 gate의 종류에 따라 결정됩니다.

Multiplication gate에서는 aibi=cia_i * b_i = c_i를 만족하므로 QLi=0,QRi=0,QOi=1,QMi=1,QCi=0Q_{L_i} = 0, Q_{R_i} = 0, Q_{O_i} = 1, Q_{M_i} = -1, Q_{C_i} = 0이 됩니다.

Addition gate에서는 ai+bi=cia_i + b_i = c_i를 만족하므로 QLi=1,QRi=1,QOi=1,QMi=0,QCi=0Q_{L_i} = 1, Q_{R_i} = 1, Q_{O_i} = -1, Q_{M_i} = 0, Q_{C_i} = 0이 됩니다.

Constant gate에서는 ci=Cc_i = C를 만족하므로 QLi=0,QRi=0,QOi=1,QMi=0,QCi=CQ_{L_i} = 0, Q_{R_i} = 0, Q_{O_i} = -1, Q_{M_i} = 0, Q_{C_i} = C가 됩니다.

총 n개의 gate가 있다고 하면, n개의 gate constraint가 존재합니다.

(QL1)a1+(QR1)b1+(QO1)c1+(QM1)a1b1+QC1=0(QLn)an+(QRn)bn+(QOn)cn+(QMn)anbn+QCn=0(Q_{L_1})a_1 + (Q_{R_1})b_1 + (Q_{O_1})c_1 + (Q_{M_1})a_1b_1 + Q_{C_1} = 0 \\ \vdots \\ (Q_{L_n})a_n + (Q_{R_n})b_n + (Q_{O_n})c_n + (Q_{M_n})a_nb_n + Q_{C_n} = 0

위 n개의 식을 하나의 식으로 compact하기 위해 다음과 같은 다항식들을 정의합니다. 각 gate들이 0,,n10, \dots, n - 1에 대응된다고 할 때, 각각 [(i,QLi)]i=1n[(i, Q_{L_i})]_{i=1}^n [(i,QRi)]i=1n[(i, Q_{R_i})]_{i=1}^n [(i,QOi)]i=1n[(i, Q_{O_i})]_{i=1}^n [(i,QMi)]i=1n[(i, Q_{M_i})]_{i=1}^n [(i,QCi)]i=1n[(i, Q_{C_i})]_{i=1}^n을 interpolate하는 (n1)(n - 1)차 다항식을 QL(x),QR(x),QO(x),QM(x),QC(x)Q_L(x), Q_R(x), Q_O(x), Q_M(x), Q_C(x)라고 정의하겠습니다.

QL(x)ai+QR(x)bi+QO(x)ci+QM(x)aibi+QC(x)=0Q_L(x)a_i + Q_R(x)b_i + Q_O(x)c_i + Q_M(x)a_ib_i + Q_C(x) = 0

그리고 각각 [(i,ai)]i=1n[(i, a_i)]_{i=1}^n [(i,bi)]i=1n[(i, b_i)]_{i=1}^n [(i,ci)]i=1n[(i, c_i)]_{i=1}^n을 interpolate하는 (n1)(n - 1)차 다항식들을 각각 a(x),b(x),c(x)a(x), b(x), c(x)로 정의하겠습니다.

QL(x)a(x)+QR(x)b(x)+QO(x)c(x)+QM(x)a(x)b(x)+QC(x)=0Q_L(x)a(x) + Q_R(x)b(x) + Q_O(x)c(x) + Q_M(x)a(x)b(x) + Q_C(x) = 0

위 등식은 x=0,,n1x = 0, \dots , n - 1일 때 성립하므로 어떤 다항식 H(x)H(x)가 존재해 다음이 성립함을 알 수 있습니다.

Z(x)=(x1)(x(n1))QL(x)a(x)+QR(x)b(x)+QO(x)c(x)+QM(x)a(x)b(x)+QC(x)=H(x)Z(x)Z(x) = (x - 1) \dots (x - (n - 1)) \\ Q_L(x)a(x) + Q_R(x)b(x) + Q_O(x)c(x) + Q_M(x)a(x)b(x) + Q_C(x) = H(x)Z(x)

Prover는 위 조건을 만족하는 다항식 a(x),b(x),c(x)a(x), b(x), c(x)를 알고 있음을 증명해야 합니다.

Copy constraint

앞서 살펴본 gate constraint에서는 예를 들어 'ii번째 gate의 output wire 값이 (i+1)(i + 1)번째 gate의 left input wire 값과 같아야 한다'는 조건은 표현되지 않았습니다. Circuit이 consistent해지기 위해서는 c(1)=b(2),  c(2)=a(4)c(1) = b(2), \;c(2) = a(4)와 같은 조건들을 만족해야 하는데, 이를 가리켜 copy constraint라고 합니다. PLONK에서는 permutation check를 통해 Prover의 witness가 copy constraint를 만족하는지 검사할 수 있습니다.

Multiset equality check

Permutation check에 대해서 알아보기에 앞서, multiset equality check에 대해서 소개하겠습니다. Multiset equality check를 통해 벡터 a=(a1,,an)a = (a_1, \dots, a_n)의 원소들과 b=(b1,,bn)b = (b_1, \dots, b_n)의 원소들을 같은 원소들끼리 대응시켰을 때 일대일 대응 관계가 성립하는지 확인할 수 있습니다. 예를 들어 (1,1,3,4)(1, 1, 3, 4)(1,3,4,1)(1, 3, 4, 1)은 multiset-equal하지만, (1,2,3,4)(1, 2, 3, 4)와는 multiset-equal하지 않습니다.

벡터 a,ba, b의 원소들이 field F\mathbb{F}에 존재할 때, γF\gamma \in \mathbb{F}인 random한 원소 γ\gamma을 선택해 다음이 성립하는지 확인합니다:

i[n](ai+γ)=i[n](bi+γ)\prod_{i \in [n]}(a_i + \gamma) = \prod_{i \in [n]}(b_i + \gamma)

Field F\mathbb{F}의 크기가 매우 클 때, 위 등식이 성립한다면 Schwartz-Zippel Lemma에 의해 (a1,,an)(a_1, \dots, a_n)(b1,,bn)(b_1, \dots, b_n)이 매우 높은 확률로 multiset-equal함을 알 수 있습니다.

Permutation check

벡터 a=(a1,,an)a = (a_1, \dots, a_n)b=(b1,,bn)b = (b_1, \dots, b_n)이 있고, permutation σ:[n][n]\sigma : [n] \rightarrow [n]이 존재할 때, i[n],  bi=aσ(i)i \in [n], \;b_i = a_{\sigma(i)}을 만족하는지 확인하기 위해 permutation check를 사용합니다.

b=σ(a)b = \sigma(a)를 만족한다는 것은, ((a1,1),,(an,n))((a_1, 1), \dots, (a_n, n))((b1,σ(1)),,(bn,σ(n)))((b_1, \sigma(1)), \dots, (b_n, \sigma(n)))이 multiset-equal이 되는 것과 필요충분조건임을 알 수 있습니다. 앞서 살펴본 multiset equality check는 단일 원소들로 이루어진 벡터에 대해서 살펴보았기 때문에, random한 값 βF\beta \in \mathbb{F}를 준비해, ((a1,1),,(an,n))((a_1, 1), \dots, (a_n, n))((b1,σ(1)),,(bn,σ(n)))((b_1, \sigma(1)), \dots, (b_n, \sigma(n)))의 각 원소들을 다음과 같이 변환합니다.

ai=ai+βibi=bi+βσ(i)a_i' = a_i + \beta * i \\ b_i' = b_i + \beta * \sigma(i)

이제 (a1,,an)(a_1', \dots, a_n')(b1,,bn)(b_1', \dots, b_n') 간에 multiset equality check를 합니다.

i[n](ai+βi+γ)=i[n](bi+βσ(i)+γ)\prod_{i \in [n]}(a_i + \beta * i + \gamma) = \prod_{i \in [n]}(b_i + \beta * \sigma(i) + \gamma)

Copy constraint

이제 permutation check를 이용해 PLONK의 copy constraint를 나타내는 방법을 살펴보겠습니다.

Arithmetic circuit의 ii번째 gate의 left input, right input, output의 값을 각각 ai,bi,cia_i, b_i, c_i라고 명명하면, 다음과 같이 총 3n3n개의 witness 값이 존재합니다.

a1,a2,,anb1,b2,,bnc1,c2,,cna_1, a_2, \dots, a_n \\ b_1, b_2, \dots, b_n \\ c_1, c_2, \dots, c_n

2번째 gate의 output wire 값이 nn번째 gate의 left input wire 값과 동일하면, an=c2a_n = c_2가 성립함을 알 수 있습니다. 그럼 다음과 같이 ana_nc2c_2의 자리를 바꿔 보겠습니다.

a1,a2,,c2b1,b2,,bnc1,an,,cna_1, a_2, \dots, c_2 \\ b_1, b_2, \dots, b_n \\ c_1, a_n, \dots, c_n

i[n]i \in [n]에 대해 aia_iii, bib_in+in + i, 그리고 cic_i2n+i2n + i에 대응된다고 하겠습니다. σ(n)=2n+2,  σ(2n+2)=n\sigma(n) = 2n + 2, \; \sigma(2n + 2) = n를 만족하는 permutation σ:[3n][3n]\sigma : [3n] \rightarrow [3n]가 존재하고, 위의 witness를 ff, 아래의 witness를 gg라고 할 때 g=σ(f)g = \sigma(f)를 만족함을 알 수 있습니다. 이는 Permutation check를 통해 다음과 같은 등식으로 표현할 수 있습니다.

i[n](ai+βi+γ)(bi+β(n+i)+γ)(ci+β(2n+i)+γ)=i[n](ai+βσ(i)+γ)(bi+βσ(n+i)+γ)(ci+βσ(2n+i)+γ)\prod_{i \in [n]}(a_i + \beta * i + \gamma)(b_i + \beta * (n + i) + \gamma)(c_i + \beta * (2n + i) + \gamma) \\[5pt] = \prod_{i \in [n]}(a_i + \beta * \sigma(i) + \gamma)(b_i + \beta * \sigma(n + i) + \gamma)(c_i + \beta * \sigma(2n + i) + \gamma) \\

마치며

이번 글에서는 PLONK arithmetization에 사용되는 math building block들에 대해서 알아보았습니다. 다음 글에서 KZG commitment에 대해 다루고 난 후, 실제 PLONK에서 Prover가 다항식을 증명하기 위한 protocol에 대해 알아보겠습니다.

profile
Researcher & Developer

4개의 댓글

comment-user-thumbnail
2023년 5월 24일

안녕하세요. 좋은 글 잘 읽었습니다. 감사합니다. 혹시 포스트 내용 관련 궁금한 점이 있는데 질문 드려도 괜찮을까요?

1개의 답글
comment-user-thumbnail
2023년 12월 18일

안녕하세요. 잘 읽었습니다. 요즘도 이쪽 공부를 하시나요? 만약 하신다면 같이 일해보고 싶네요.
본문에서 (개인적인 해석이니, 잘못 이해한 것이라면 댓글로 코멘트 남겨주세요!)표시하신 내용에 관해 제 의견을 드리고자 합니다.

"R1CS의 서킷 크기가 Plonkish 서킷보다 비대하다."
저는 이 문장에 동의하지 않습니다. Plonk가 R1CS의 quadratic 형태의 constraint를 버리고 Sonic과 비슷하게 linear constraint를 도입한 이유는 써킷 크기를 줄이기 위해서가 아니라, copy constraint를 적용하기 위해서입니다. 더 구체적으로, copy constraint와 arithmetic constraint를 분리한 이유는, trust setup에 한정되지 않고 한 번의 setup으로 다양한 써킷을 표현하기 위한 "자유도"를 얻기 위함입니다. 자유도의 대가(cost)는 안타깝게도 써킷의 비효율화입니다 (custom gate가 적용되면 어떻게 될 지 모르겠으나, 일단 vanilla plonkish 기준으로 말씀드립니다).

그 이유는 R1CS에 이미 copy constraint가 내포되어있기 때문입니다. 예시를 보여드리겠습니다.
"x^3+x^2=y" 라는 연산을 고려하겠습니다. 곱하기 3번에 더하기 1번이 들어가네요.

Copy constraint를 고려하지 않고 plonkish처럼 R1CS회로를 짜면:
1. l_1r_1=o_1
2. l_2
r_2=o_2
3. l_3r_3=o_3
4. (l_4+l_5)
1=o_4
이 회로에 4개의 gate에 와이어가 12개가 사용되었네요. Plonkish의 와이어 수도 gate수*3이므로 동일하죠.
여기에 copy constraint는 l_1=r_1=r_2=l_3=r_3 (모두 x가 할당됨), l_2=o_1=o_3=l_5 (x^2이 할당됨), l_4=o_2 (x^3이 할당됨).

이제 Copy constraint를 적용해서 위 회로를 다시 쓰면:
1. l_1l_1=o_1
2. o_1
l_1=o_2
3. l_1l_1=o_1
4. (o_2+o_1)
1=o_4
이 회로에서 1번과 3번 constraint는 중복이니 하나가 사라져도 회로의 역할에는 영향을 주지 않습니다. 따라서 3개의 gate와 4개의 와이어로 회로가 표현되네요.

R1CS를 사용하는 Pinocchio와 Groth16은 이미 copy constraint를 다 적용 해 놓은 상태로 crs에 회로를 구워놓습니다. 따라서 회로가 더 효율적이고, 따로 permutation argument같은 걸로 추가 검증을 할 필요가 없습니다.

꼭 copy constraint때문이 아니더라도, R1CS 회로가 일반적으로 plonkish 회로보다 크기가 더 작습니다. 예를 들어, "x_0+x_1+...+x_9=y" 라는 연산을 회로로 만들면, plonkish는 10개의 게이트가 필요한 반면 R1CS는 게이트가 하나만 있으면 됩니다. R1CS는 "곱하기"만을 하나의 constraint로 취급하는 반면, Plonkish는 곱하기와 더하기 각각을 하나의 constraint로 취급합니다.

1개의 답글