Workspace IndexAlgorithms › Day 91

Arithmetization — R1CS, AIR, and PLONKish TODO

Algorithms · Day 91 / 100 · F. Cryptography & ZK (Day 82-96)

Concept

Arithmetization is the step that turns the claim "this program executed correctly" into a polynomial constraint-satisfaction problem over a finite field — it's the first gate every ZK proof system passes through. R1CS expresses a computation as a set of constraints of the form A·z ∘ B·z = C·z, where z is a vector holding the public inputs and the witness, and each constraint corresponds to one multiplication gate; the representation is simple, but the constraint count grows in proportion to the number of multiplications. AIR views the computation as an execution trace table and expresses it through transition constraints — what must hold between two adjacent rows — plus boundary constraints; this fits VM execution, where the same operation repeats, especially well, making the constraint description very compact. PLONKish is an arithmetization built on a table of columns and rows, layered with arbitrary-degree custom gates, copy constraints (a permutation argument) that force different cells to be equal, and lookup arguments — its big advantage is that expensive operations like bit manipulation can be replaced with lookups into a precomputed table. The three approaches have comparable expressive power, but differ in circuit size, proving time, and setup requirements, so the real-world choice depends on the shape of the target computation.

In a ZK system, proving cost is mostly determined by how large the circuit grows at the arithmetization stage, so the same logic can be practical or not depending on which arithmetization it's compiled into.

Code & Formula

# 산술화 — R1CS·AIR·PLONKish
# y = x^3 + x + 5 (x=3 -> y=35) 를 R1CS 제약 A·z ∘ B·z = C·z 로 평탄화해 검증한다.

# witness 벡터 z = [1, out, x, sym1, y]  (0=상수, 1=공개출력, 2=입력, 3~4=중간값)
IDX = {"one": 0, "out": 1, "x": 2, "sym1": 3, "y": 4}
N = len(IDX)

def row(**coeffs):
    r = [0] * N
    for name, c in coeffs.items():
        r[IDX[name]] = c
    return r

# 제약 1: x * x = sym1
# 제약 2: sym1 * x = y
# 제약 3: (y + x + 5*one) * one = out
A = [row(x=1), row(sym1=1), row(y=1, x=1, one=5)]
B = [row(x=1), row(x=1), row(one=1)]
C = [row(sym1=1), row(y=1), row(out=1)]

def dot(r, z):
    return sum(a * b for a, b in zip(r, z))

def check_r1cs(z):
    for a, b, c in zip(A, B, C):
        if dot(a, z) * dot(b, z) != dot(c, z):
            return False
    return True

x = 3
sym1 = x * x
y = sym1 * x
out = y + x + 5
z = [1, out, x, sym1, y]

print("witness z =", z)
print("R1CS 제약 3개 모두 만족:", check_r1cs(z))

bad_z = z.copy()
bad_z[IDX["out"]] += 1  # 결과를 조작하면
print("조작된 out 은 거부됨:", not check_r1cs(bad_z))

Exercise

Take a small expression like x^3 + x + 5 = 35 and flatten it into R1CS constraints by hand — write out the A, B, C matrices and the witness vector yourself — then express the same computation as transition constraints between two rows (AIR).

Practical Connection

When evaluating a design where order matching or settlement in a prediction market is computed off-chain and only the result is posted on-chain as a proof, you need to know how expensive operations like comparisons and division are inside a circuit to judge the break-even point against doing the computation on-chain.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

산술화 TODO

Algorithms · Day 91 / 100 · F. 암호학·ZK (Day 82–96)

R1CS·AIR·PLONKish

개념

산술화는 '이 프로그램을 올바르게 실행했다'는 명제를 유한체 위의 다항식 제약 만족 문제로 바꾸는 단계로, 모든 ZK 증명 시스템의 첫 관문이다. R1CS는 계산을 A·z ∘ B·z = C·z 형태의 제약 집합으로 표현하는데, 여기서 z는 공개 입력과 witness를 담은 벡터이고 각 제약은 곱셈 게이트 하나에 대응하므로 표현이 단순한 대신 제약 수가 곱셈 개수에 비례해 커진다. AIR는 계산을 실행 트레이스 표로 보고 '인접한 두 행 사이에 성립해야 하는 전이 제약'과 경계 제약으로 표현하며, 같은 연산이 반복되는 VM 실행에 특히 잘 맞아 제약 기술이 매우 간결해진다. PLONKish는 열(column)과 행(row)으로 이루어진 표에 임의 차수의 커스텀 게이트, 서로 다른 셀을 동일하게 강제하는 copy constraint(순열 논증), 그리고 lookup 논증을 얹은 산술화로, 비싼 비트 연산 같은 것을 미리 계산된 테이블 조회로 대체할 수 있다는 점이 큰 장점이다. 세 방식은 표현력은 비슷하지만 회로 크기, 증명 시간, 셋업 요구사항이 달라 실제 선택은 대상 계산의 형태에 좌우된다.

ZK 시스템의 증명 비용은 대부분 산술화 단계에서 회로가 얼마나 커지느냐로 결정되므로, 같은 로직도 어떤 산술화에 올리느냐에 따라 실용성이 갈린다.

코드 · 수식

# 산술화 — R1CS·AIR·PLONKish
# y = x^3 + x + 5 (x=3 -> y=35) 를 R1CS 제약 A·z ∘ B·z = C·z 로 평탄화해 검증한다.

# witness 벡터 z = [1, out, x, sym1, y]  (0=상수, 1=공개출력, 2=입력, 3~4=중간값)
IDX = {"one": 0, "out": 1, "x": 2, "sym1": 3, "y": 4}
N = len(IDX)

def row(**coeffs):
    r = [0] * N
    for name, c in coeffs.items():
        r[IDX[name]] = c
    return r

# 제약 1: x * x = sym1
# 제약 2: sym1 * x = y
# 제약 3: (y + x + 5*one) * one = out
A = [row(x=1), row(sym1=1), row(y=1, x=1, one=5)]
B = [row(x=1), row(x=1), row(one=1)]
C = [row(sym1=1), row(y=1), row(out=1)]

def dot(r, z):
    return sum(a * b for a, b in zip(r, z))

def check_r1cs(z):
    for a, b, c in zip(A, B, C):
        if dot(a, z) * dot(b, z) != dot(c, z):
            return False
    return True

x = 3
sym1 = x * x
y = sym1 * x
out = y + x + 5
z = [1, out, x, sym1, y]

print("witness z =", z)
print("R1CS 제약 3개 모두 만족:", check_r1cs(z))

bad_z = z.copy()
bad_z[IDX["out"]] += 1  # 결과를 조작하면
print("조작된 out 은 거부됨:", not check_r1cs(bad_z))

연습

x^3 + x + 5 = 35 같은 작은 식을 손으로 R1CS 제약으로 평탄화해 A, B, C 행렬과 witness 벡터를 직접 써 보고, 같은 계산을 두 행 사이의 전이 제약(AIR)으로도 표현해 보라.

실무 · Verex 연결

예측시장에서 주문 매칭이나 정산 계산을 오프체인에서 하고 결과만 증명으로 올리려는 설계를 검토할 때, 비교·나눗셈 같은 연산이 회로에서 얼마나 비싼지를 알아야 온체인 계산과의 손익 분기를 판단할 수 있다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 90. 유한체·다항식 산술과 NTT 구현 관점92. 다항식 IOP →