안녕하세요, BQ개발자입니다.
ZK 개발을 시작하려는 분들을 위한 시리즈를 시작합니다.
첫 주제는 유한체입니다.
Circom이든 Noir이든, 모든 ZK 언어는 유한체 위에서만 동작합니다.
일반 정수가 아니라요. 이걸 모르면 내 코드가 왜 이상하게 동작하는지 이해할 수 없습니다.
❓ 유한체란?
컴퓨터는 숫자를 저장할 때 정해진 칸을 사용합니다. uint256은 256개의 0과 1로 숫자를 표현하는데,
가장 큰 수는 115792089237316195423570985008687907853269984665640564039457584007913129639935입니다. 78자리 숫자죠.
이 숫자에 1을 더하면 어떻게 될까요? 257번째 자리가 필요한데, 그런 공간이 없습니다.
그래서 모든 자리가 0으로 리셋됩니다. 실제로 100억 원이 있던 토큰 잔액이 1원을 더 받는 순간 0원이 되어버린 해킹 사고가 있었습니다.
유한체는 이 "최댓값 다음이 0"이라는 현상을 아예 정상 동작으로 만들어버린 시스템입니다. 버그가 아니라 원래 그렇게 설계한 거죠.
솔리디티 개발자라면 늘 조심하는 상황이 있습니다.
uint256 최댓값에 1을 더하면 0이 되기 때문이죠.
다시 말해 256비트로 표현할 수 있는 한계를 넘어서면, 다시 0으로 돌아가는 상황을 겪게되고,
이게 정수 오버플로우입니다.
TimeLock 해킹이 바로 이걸 악용했던 사례죠
미래 시점의 잠금에 큰 수를 더해서 0으로 만들어버린 다음
즉시 출금이 가능하도록 해서 해킹이 가능했습니다..ㄷㄷ
이 문제를 해결하려고 개발자들은 계속 방어 코드를 추가해왔습니다.
SafeMath 라이브러리를 쓰거나, Solidity 0.8.0부터는 컴파일러가 자동으로 체크하죠
하지만 이건 비용입니다. 모든 연산마다 "오버플로우 체크"가 추가로 실행되니까요.
문제의 핵심은 이거죠.
일반 정수 시스템에서 오버플로우는 예외적 오류라는점
그래서 막아야 하고, 막는 데 비용이 듭니다.
ZK에서 사용하는 유한체 Fp를 보겠습니다.
0부터 p-1까지의 정수로 이루어져 있고, 모든 연산이 mod p로 정의됩니다.
p-1에 1을 더하면? 0이 됩니다.
이건 오류가 아니라 정의죠.
유한체는 이렇게 처음부터 "순환하는 시스템"으로 설계되었습니다.
p를 넘으면 다시 0부터 시작하는 게 정상 동작이죠.
차이를 정확히 짚어보겠습니다.
일반 정수에서 오버플로우는 보통 예외상황으로 봅니다.
대부분의 연산은 정상 범위에서 일어나고, 가끔 경계를 넘을 때만 문제가 생기죠.
그래서 매번 체크해야 합니다.
하지만 유한체에서는 순환이 정상입니다.
애초에 모든 연산이 mod p로 정의되어 있으니, 체크할 필요가 없습니다.
오버플로우라는 개념 자체가 없는 거죠.
Circom에서 그냥 `sum <== a + b`라고 쓸 수 있는 이유입니다.
결과가 p를 넘든 안 넘든, 항상 유효한 값이 나오니까요.
✏️ 버그가 사라진 세계
유한체를 처음 접했을 때 깨달은 점이 있습니다.
우리가 "오버플로우 버그"라고 부르던 것은, 사실 무한을 유한한 공간에 억지로 담으려다 생긴 문제였다는점!
당연하게도 메모리는 유한한데 거기에 무한하게 큰 수를 담을 수 없으니까요.
유한체는 처음부터 유한하다고 가정합니다.
그래서 모든 숫자가 0부터 p-1 사이에 있고, 모든 연산 결과도 그 안에 있도록 하는거죠
무한을 다루려는 시도 자체가 없으니, 오버플로우도 없습니다.
ZK 회로를 작성할 때 SafeMath 같은 게 필요 없는 이유가 여기 있습니다.
오버플로우 걱정 자체를 할 필요가 없으니까요.
다음 편에서는 "음수가 없는데 뺄셈은 어떻게?"를 다뤄보겠습니다.
이것도 유한체의 독특한 접근 방식이 있더라구요!!
---
여러분의 좋아요와 공유는 사랑입니다 🥰
#BQ개발자 #ZK개발 #유한체 #Circom #영지식증명
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7
안녕하세요, BQ개발자입니다.
지난 편에서 유한체는 오버플로우가 정상 동작이라고 했습니다.
오늘은 제가 이해하는 데 시간이 좀 걸렸던 개념을 다룹니다.
ZK 회로를 디버깅하다가 이상한 현상을 발견할 수 있습니다.
5 - 10을 계산했는데 `21888242871839275222246405745257275088696311157297823662689037894645226208578`이 나오는 거죠.
버그인 줄 알고 한참 헤멜 수 있지만, 알고 보면 정상입니다.
일반적으로 5 - 10 = -5죠.
그런데 유한체에는 음수가 없습니다. 0부터 p-1까지만 있어요.
그럼 5 - 10은 어떻게 표현될까요?
유한체 Fp에서는 0 아래로 가면 반대편 끝으로 돌아옵니다.
0 - 1 = p - 1이 되는 거죠.
5 - 10 = p - 5가 되고, 이게 바로 그 78자리 숫자입니다.
신기한 건 이 큰 수가 정확히 -5처럼 동작한다는 점입니다.
여기에 5를 더하면 0이 되고, 15를 더하면 10이 되거든요.
1️⃣ 토큰 전송 로직의 변화
일반 프로그래밍에서 토큰 전송을 구현할 때는 항상 잔액을 먼저 확인합니다.
잔액이 부족하면 음수가 되니까 막아야 하죠.
유한체에서는 다르게 접근한다고 합니다.
잔액 5에서 10을 빼면 그 거대한 78자리 숫자가 나옵니다.
정상적인 잔액은 보통 20자리 이하인데, 갑자기 78자리가 나온 거죠.
이 "비정상적으로 큰 값"을 걸러내는 방식으로 부정한 전송을 방지한다고 하네요.
조건문 대신 값의 크기로 판단하는 거죠.
2️⃣ 회로가 단순해지는 효과
ZK 회로에서 if-else 같은 분기문은 구현이 복잡하다고 합니다.
"조건이 참이면 A, 거짓이면 B"를 회로로 만들려면,
양쪽 경로를 다 계산한 뒤 선택하는 구조가 필요하다고 하더라고요.
유한체를 활용하면:
- 조건 확인 없이 바로 계산
- 결과값의 크기로 유효성 판단
직선적인 연산으로 처리되니 회로가 간단해진다고 합니다.
3️⃣ 제약이 오히려 편리함을 만드는 경우
처음엔 음수가 없다는 게 불편할 것 같았습니다.
그런데 생각해보니, 음수를 따로 관리하지 않아도 되는 거...?
부호 비트도 필요 없고, 양수인지 음수인지 체크하는 코드도 필요 없죠.
모든 값을 동일한 방식으로 처리할 수 있다는 점이 오히려 장점인 것 같습니다.
✏️ 유한체를 공부하면서 느낀 점 : 없는 게 있는 것
유한체 개념을 보면 음수가 "없다"고 하지만, 사실은 큰 양수로 "있는" 거라는점,
단지 표현 방식이 다를 뿐, 수학적 역할은 동일합니다.
수학이 정말 재미있는 건, 없애버린 것(음수)이 사실은 다른 형태(큰 양수)로 완벽하게 존재한다는 점인 것 같습니다.
제약을 만들었더니 오히려 더 자유로워진 거죠.
-5든 p-5든, 5를 더하면 0이 되는 건 같으니까..?
---
여러분의 좋아요와 공유는 사랑입니다 🥰
#BQ개발자 #ZK개발 #유한체 #영지식증명 #뺄셈
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3