Workspace IndexAlgorithms › Day 93

Recursive proofs and proof aggregation TODO

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

Concept

A recursive proof proves the very fact that some other proof passed verification, by expressing the proof-verification algorithm as a circuit and then proving the execution of that circuit. This lets you fold an arbitrarily long computation, or the state transitions across many blocks, into a single small proof, so verification cost becomes independent of the length of the original computation. Proof aggregation bundles multiple independent proofs to cut verification cost overall; it's sometimes implemented via recursion, and sometimes via cheaper techniques like batch verification or linear combinations of commitments. The key practical constraint is how cheaply that proof system's verifier can be expressed inside a circuit, which is why curve choice and proof-friendly hash function choice matter so much. The essence of the technique is an asymmetry of cost: the prover gets heavier while the verifier becomes extremely light.

This structure is exactly why a rollup can settle enormous numbers of transactions with a single verification on L1, and why a light client can catch up on a long history at low cost.

Code & Formula

# 재귀 증명과 증명 집계 — 여러 스텝의 증명을 접어 "이전까지 전부 유효했음"을
# 상수 크기 하나로 압축하는 폴딩(재귀 검증)을 해시 체인으로 단순화해 시연.
# (실제 SNARK 재귀와 달리 여기선 검증도 재실행하지만, 접힘/집계의 구조만 보여주는 예시)

import hashlib

def h(*parts: bytes) -> bytes:
    m = hashlib.sha256()
    for p in parts:
        m.update(p)
    return m.digest()

def step_proof(prev_proof: bytes, statement: bytes) -> bytes:
    # "이 스텝이 유효하다"는 증명을 이전 증명과 접어(fold) 하나의 값으로 만든다
    return h(prev_proof, statement)

def verify_chain(genesis: bytes, statements: list, final_proof: bytes) -> bool:
    # 재귀 증명이라면 검증자는 final_proof 하나만 확인하면 되지만,
    # 여기서는 폴딩 구조 설명을 위해 재실행으로 대신 확인한다.
    acc = genesis
    for s in statements:
        acc = step_proof(acc, s)
    return acc == final_proof

genesis = h(b"genesis")
statements = [f"tx-{i}".encode() for i in range(5)]

# 증명자: 각 스텝을 순서대로 접어 하나의 집계 증명(final_proof)을 만든다
acc = genesis
individual_sizes = 0
for s in statements:
    acc = step_proof(acc, s)
    individual_sizes += len(acc)
final_proof = acc

print("스텝 수:", len(statements))
print("집계 전 개별 증명 총 크기(byte):", individual_sizes)
print("집계된 최종 증명 크기(byte):", len(final_proof), "← 스텝 수와 무관하게 일정")
print("최종 증명 검증 결과:", verify_chain(genesis, statements, final_proof))

tampered = statements.copy()
tampered[2] = b"tx-2-tampered"
print("중간 statement 조작 시 검증 실패:", not verify_chain(genesis, tampered, final_proof))

Exercise

Pick one ZK framework, generate a proof for a tiny circuit, then write a circuit that verifies that proof to run one round of recursion — measure how proof size and proving time each change.

Practical Connection

When Verex runs on an L2 or trusts an L2's settlement, the user's basis for trusting finality ultimately comes down to this one act of proof verification, and the proof-generation cycle directly shows up as withdrawal/settlement latency.

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 93 / 100 · F. 암호학·ZK (Day 82–96)

개념

재귀 증명은 어떤 증명이 검증을 통과했다는 사실 자체를 다시 증명하는 기법으로, 증명 검증 알고리즘을 회로로 표현한 뒤 그 회로의 실행을 증명하는 방식으로 구현한다. 이렇게 하면 임의로 긴 계산이나 여러 블록의 상태 전이를 하나의 작은 증명으로 접을 수 있어, 검증 비용이 원래 계산의 길이와 무관해진다. 증명 집계는 여러 개의 독립적인 증명을 묶어 검증 비용을 줄이는 것으로, 재귀로 구현하기도 하고 배치 검증이나 커밋먼트의 선형 결합 같은 더 값싼 방법을 쓰기도 한다. 실무의 핵심 제약은 그 증명 시스템의 검증기를 회로 안에서 얼마나 싸게 표현할 수 있는가이며, 그래서 곡선 선택이나 증명 친화적 해시 함수 선택이 중요해진다. 이 기술의 본질은 비용의 비대칭으로, 증명자는 무거워지고 검증자는 극단적으로 가벼워진다.

롤업이 수많은 트랜잭션을 L1에서 한 번의 검증으로 정산하고 라이트클라이언트가 긴 히스토리를 작은 비용으로 따라잡을 수 있는 근거가 바로 이 구조이기 때문이다.

코드 · 수식

# 재귀 증명과 증명 집계 — 여러 스텝의 증명을 접어 "이전까지 전부 유효했음"을
# 상수 크기 하나로 압축하는 폴딩(재귀 검증)을 해시 체인으로 단순화해 시연.
# (실제 SNARK 재귀와 달리 여기선 검증도 재실행하지만, 접힘/집계의 구조만 보여주는 예시)

import hashlib

def h(*parts: bytes) -> bytes:
    m = hashlib.sha256()
    for p in parts:
        m.update(p)
    return m.digest()

def step_proof(prev_proof: bytes, statement: bytes) -> bytes:
    # "이 스텝이 유효하다"는 증명을 이전 증명과 접어(fold) 하나의 값으로 만든다
    return h(prev_proof, statement)

def verify_chain(genesis: bytes, statements: list, final_proof: bytes) -> bool:
    # 재귀 증명이라면 검증자는 final_proof 하나만 확인하면 되지만,
    # 여기서는 폴딩 구조 설명을 위해 재실행으로 대신 확인한다.
    acc = genesis
    for s in statements:
        acc = step_proof(acc, s)
    return acc == final_proof

genesis = h(b"genesis")
statements = [f"tx-{i}".encode() for i in range(5)]

# 증명자: 각 스텝을 순서대로 접어 하나의 집계 증명(final_proof)을 만든다
acc = genesis
individual_sizes = 0
for s in statements:
    acc = step_proof(acc, s)
    individual_sizes += len(acc)
final_proof = acc

print("스텝 수:", len(statements))
print("집계 전 개별 증명 총 크기(byte):", individual_sizes)
print("집계된 최종 증명 크기(byte):", len(final_proof), "← 스텝 수와 무관하게 일정")
print("최종 증명 검증 결과:", verify_chain(genesis, statements, final_proof))

tampered = statements.copy()
tampered[2] = b"tx-2-tampered"
print("중간 statement 조작 시 검증 실패:", not verify_chain(genesis, tampered, final_proof))

연습

ZK 프레임워크 하나를 골라 아주 작은 회로의 증명을 만든 뒤 그 증명을 검증하는 회로를 다시 작성해 재귀 한 단계를 돌리고, 증명 크기와 증명 시간이 각각 어떻게 변하는지 측정하기.

실무 · Verex 연결

Verex가 L2 위에서 동작하거나 L2 정산을 신뢰할 때 사용자가 최종성을 믿는 근거는 결국 이 증명 검증 한 번이며, 여기서 증명 생성 주기가 곧 출금·정산 지연으로 나타난다.

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

← 92. 다항식 IOP94. 프라이버시 프리미티브 →