rkm0959's profile image

rkm0959

April 25, 2024 00:00

Insights from and on Taxonomy of ZKP Vulnerabilities

cryptography , blockchain

올해 4월 초, ZKP 관련 최대 컨퍼런스 중 하나인 zkSummit11에서 ZKP 보안과 관련된 발표를 진행할 수 있었습니다. 이번 글에서는 발표 내용에 대한 간략한 설명을 합니다.

ZKP 보안에 대한 업계의 이해도는 2022년부터 꾸준히 증가해왔지만, 아직도 ZKP 보안 판에는 여러 문제가 있습니다. 가장 큰 문제 중 하나는 사람이 부족하다는 것인데, 이에 대한 해답으로는 크게 두 가지를 생각할 수 있습니다.

  • 암호를 잘 하는 사람에게 보안의 컨텍스트를 가르쳐 ZKP 보안을 소개하는 것
  • 블록체인 보안을 하는 사람에게 암호의 컨텍스트를 가르쳐 ZKP 보안을 소개하는 것

이 글에서는 후자에 대해서 다룹니다. 블록체인 보안을 잘하는 사람들이 ZKP 보안에 관심을 가지는 경우가 꽤 있는데, 대부분 필요한 수학에 대한 이해가 부족하여 어려움을 겪습니다. 이 장벽을 낮추기 위해, ZKP 보안을 위해서는 정확히 어떤 수학이 어떤 일을 할 때 필요하며, 왜 필요한지를 분석하는 것이 유용할 것이라고 생각했습니다.

발표 자료는 이 링크에 있습니다.

ZKP 보안은 왜 어려운가

기본적으로 ZKP는 prover가 $y = f(x)$라는 계산을 정확하게 진행했음을 증명하는 수단으로 사용됩니다. Prover는 이 연산에 대한 증명 $\pi$를 계산하고, Verifier는 이 증명 $\pi$를 검증하여 $y$라는 값에 대한 암호학적 신뢰를 얻을 수 있습니다. 이 증명 $\pi$를 계산하는 프로그램을 짜기 위해서, 엔지니어들은 $f$를 “ZKP-friendly”한 연산들로 소화시켜 구현해야 합니다. 이 과정에서 실수가 발생하면, 실제로 증명하는 명제가 $y = f(x)$가 아니게 되어, $y \neq f(x)$인 경우에도 증명이 검증되는 등 여러 문제가 발생하게 됩니다.

결국 이 “ZKP circuit”을 짜는 과정에서는, 문제가 연산을 ZKP-friendly 하게 쪼개는 과정에서 발생합니다. 다르게 말하면, ZKP circuit이 지원하는 연산 구조의 특징을 파악하면 버그가 어떻게 발생하는지를 역으로 추론할 수도 있습니다. 최근 가장 많이 사용되는 arithmetization은 PLONKish니, 그 특징과 버그 종류 사이의 대응을 생각할 수 있겠습니다.

예를 들면,

  • $\mathbb{F}_p$를 사용한다는 사실이 overflow/underflow 버그를 만듭니다.
  • 사용할 수 있는 constraint들이 제한적입니다. 이는 어떤 witness를 계산할 때 circuit 밖에서 계산하고, 이를 assign 한 다음, implicit 하게 constraint를 거는 게 필요할 때가 있다는 것을 의미합니다. 하지만 이때 constraint를 거는 것을 까먹으면 assign만 되고 constraint가 안되게 됩니다. 이러한 버그 계열 역시 자주 보이는 버그입니다.
  • PLONKish 특성상 constraint들이 고정되어 있기 때문에, 길이가 variable인 (instance 마다 다른 길이를 갖는) 구조체를 다루는 것이 쉽지 않습니다. 이에 따라서 버그가 발생하기도 합니다.
  • PLONKish 특성상 constraint의 종류를 boundary와 transition으로 나눠서 거는 경우가 많습니다. 이때 둘 중 하나를 까먹기가 매우 쉽고, 이를 까먹으면 underconstrain이 발생합니다.
  • PLONKish가 지원하는 연산을 최적으로 활용하기 위한 암호학적/수학적 트릭이 쓰이는 경우가 많은데, 여기서 실수를 하기 쉽습니다.

한편, prover/verifier의 로직은 정확하지만 이를 전체 시스템에 integrate 시키는 과정에서 실수가 발생할 수도 있습니다. 하지만 자세하게 다뤄보면, 여기서 발생하는 버그 대부분은 많은 수학을 필요로 하지 않음을 알 수 있습니다. 가장 수학적인 계열의 버그는 malleability에 대한 것으로, 한 명제에 대한 증명을 다른 명제에 대한 증명으로 변환시킬 수 있는지에 대한 문제를 다룹니다. ZKP의 malleability 여부는 상당히 수학적인 문제지만, 실제 시스템의 버그로 이어질 수 있기 때문에 이해가 필수적입니다.

실제로 필요한 수학은 어디까지일까

취약점들의 종류를 보고, 이들을 파악할 수 있는 수학적 능력을 갖추기 위한 사전지식을 생각해보면 대강

  • ZKP의 목적과 성질에 대한 전반적인 이해
  • PLONKish arithmetization과 $\mathbb{F}_p$에 대한 이해
  • PLONKish circuit을 짜는데 사용되는 암호학적/수학적 트릭들
  • proof malleability에 대한 이해

입니다. 결국, 이 집합은 표면에 드러난 암호학/수학과 같다고 볼 수 있습니다.

하지만 보안 업무를 실제로 수행할 때 필요한 암호학/수학을 다루는 경우, 결론을 도출하는 과정에서 사용되는 논리는 복잡할 수 있어도, 결론만 필요한 경우가 많습니다. 즉, 결론을 블랙박스로 사용해도 괜찮은 경우가 많습니다. 물론, 보안 업무 특성상 블랙박스로 환원하는 과정에서 정보의 손실이 있으면 안되는데, 실제로 정보 손실이 없다는 것을 확인할 수 있습니다. 블랙박스를 사용하여 필요한 암호학/수학의 양을 줄일 수 있다는 것이 제 생각이었습니다.

예를 들어, malleability를 생각하면, 결과 자체는 다음과 같습니다.

  • KZG 기반은 (PLONK 등) simulation-extractable 해서 non-malleable 입니다. (2023/569)
  • Groth16은 malleable 하지만 이를 막는 dummy constraint가 백엔드 단에서 추가되는 경우가 많습니다.

그러므로, 결론은 “가장 단순한 malleability attack”을 제외하면 공격이 불가능하다는 것입니다. 이 결론을 도출하는 과정은 상당히 어려운 논문 몇 편이 필요하지만, 결과만 알아도 문제가 없습니다.

비슷하게, Fiat-Shamir 트릭과 같은 경우도, 필요한 결론은 다음과 같습니다.

  • “transcript에 현재 계산 가능한 모든 값이 내재하면, 그리고 내재해야만 안전하다”

예를 들어서, $c$가 $a$와 $b$를 concat 한 결과임을 증명하기 위해서

\[\text{RLC}(a) \cdot \gamma^{n_b} + \text{RLC}(b) = \text{RLC}(c)\]

를 확인한다고 합시다. 이때,

\[\text{RLC}(a_0, \cdots, a_{n-1}) = \sum_{i=0}^{n-1} a_i \cdot \gamma^{n-1-i}\]

인데, 여기서 제일 핵심은 $\gamma$를 어떻게 결정하는지입니다. $a, b$를 안다면, $c$도 강제되므로, $\gamma = H(a, b, c)$로 두면 됩니다. 특히, $c$를 빼먹고 $\gamma = H(a, b)$로 둔다면, $a, b$를 고정하고 $\gamma$를 고정시킨 다음, RLC 조건을 만족시키는 다른 $c$를 적당히 찾는 공격을 생각할 수 있습니다. 물론, 실제로 공격을 끝까지 성공시키는 것은 더 어려운 문제지만, 공격을 실제로 성공시키는 것보다 문제점을 찾는 게 더 중요한 환경에서는 큰 문제가 되지 않습니다.

결국 Fiat-Shamir도 그 기반이 되는 논리는 쉽지 않지만, 그 결론은 어느 정도 간단하며 충분한 시간을 사용하면 직관적으로 이해할 수 있음을 (그리고 이게 충분함을) 알 수 있습니다.

또한, 라이브러리가 사용된다면 표면에 드러난 암호학/수학의 양을 줄일 수 있습니다. 예를 들어, uint256 연산을 직접 ZKP로 구현해야 한다면 이를 최적화하기 위한 암호학/수학을 다 알고 보안감사를 해야겠지만, uint256을 구현한 라이브러리를 직접 사용하고 있는 상황인데다 uint256 라이브러리는 보안감사가 이미 완료된 코드라면, 단순히 라이브러리의 가정 및 올바른 사용만 확인하면 충분합니다. 즉, 라이브러리에 내재된 수학적 트릭을 볼 필요가 없어져, 사전지식의 필요한 양을 줄일 수 있습니다.

그럼에도 수학이 필요한 이유

일단 함수 $f$ 자체가 상당히 암호학적/수학적인 경우가 있습니다. 당연히 $f$의 ZKP 구현이 안전함을 확인하기 위해서 $f$ 자체에 대한 이해도가 필요하기 때문에, 이 경우에는 사전지식이 필수적입니다. $f$가 EdDSA verification이라면, EdDSA verification에 대해서 충분히 이해하고 있어야 합니다.

또한, 앞서 라이브러리가 충분히 강력한 경우에는 표면 위의 암호/수학이 적어져 사전지식이 줄어든다고 언급했는데, 이 트렌드를 생각해보면 결국 결과가 zkVM과 같은 프로덕트로 이어짐을 생각해볼 수 있습니다. 즉, VM execution 자체가 ZKP로 덮이는 경우, 사람들이 프로그램을 짜기 위해서 직접 circuit을 짜는 게 아니라, VM 위에서 작동하는 machine 코드로 컴파일되는 언어를 구현한 후 VM을 사용하는 방식을 사용하게 될 것을 예상할 수 있습니다. 이는 실제로 최근 가장 많이 연구되고 있는 분야 중 하나로, circuit 대신 code/program을 짜는 시대가 다가오고 있습니다. 이 경우, circuit에 대한 ZKP 보안이 사실상 ZKP 보안이 아니게 되어, 특별한 의미가 없어지게 될 수도 있습니다.

이 시나리오에서 남은 “ZKP 보안”은 크게 두 가지인데,

  • zkVM 그 자체에 대한 보안감사
  • zkVM을 거치지 않고 직접 짠 circuit에 대한 보안감사

이 경우, 전자를 하는 팀이 엄청나게 많은 것은 아니라는 점과 이때 $f$가 VM에 대한 것인만큼 복잡하다는 점, 그리고 이 팀들이 성능 개선을 위해 뒷단에 있는 암호학을 엄청나게 최적화할 것이라는 점을 생각하면 이들을 다룰 때도 암호학/수학을 전반적으로 다 이해하고 있는 것이 좋을 것이라고 추측할 수 있습니다.

후자의 경우는 zkVM을 통해서 하기에는 performance가 너무나도 중요해서 직접 circuit을 구현한 경우일 가능성이 높습니다. 그런데 그 정도로 performance가 중요하다면, 라이브러리를 직접 사용한다기 보다는 가능한 할 수 있는 모든 최적화를 동원하기 위해서 상황과 cost metric에 맞는 라이브러리를 직접 작성하고 최대한의 최적화를 할 것이라고 예측할 수 있습니다. 즉, 라이브러리를 통해 암호학/수학이 덜 필요해지는 시나리오를 완벽하게 적용하기에는 어려울 것이라고 볼 수 있습니다.

결국 암호학/수학을 깊게 이해하는 것은 ZKP 판이 어떻게 진화하던 상관없이 일을 잘 할 수 있는 기반이 되어주기에, ZKP 판의 발전 방향에 대한 베팅을 하고 싶지 않은 경우에는 수학을 깊게 아는 것이 좋다는 게 제 생각입니다.