스마트 계약의 형식 검증과 AI 보조 감사
스마트 계약 감사에서의 형식 검증
형식 검증은 수학적 및 논리 기반 기술을 사용하여 스마트 계약의 정확성과 보안을 증명하는 것을 의미합니다. 실제로 이는 속성 기반 퍼즈 테스트와 심볼릭 실행부터 엄격한 정리 증명 및 모델 체킹에 이르기까지 다양한 방법론을 포함합니다. 목표는 계약이 명세를 충족하고 모든 가능한 입력과 상태에 걸쳐 악용 가능한 버그가 없음을 보장하는 것입니다. DeFi 프로토콜에 수십억 달러가 예치되어 있는 높은 위험을 고려할 때, 형식적 방법은 이더리움 및 기타 블록체인 플랫폼에서 점점 더 중요해지고 있습니다.
전통적인 접근 방식: 이더리움을 위한 초기 형식적 방법에는 Oyente 및 Mythril과 같은 심볼릭 실행 도구와 Slither 및 Securify와 같은 정적 분석기가 포함되었습니다. 심볼릭 실행은 심볼릭 입력을 사용하여 프로그램 경로를 탐색하여 재진입, 정수 오버플로우 등의 문제를 감지하는 반면, 정적 분석은 규칙 기반 패턴 매칭을 사용합니다. 이러한 도구들은 성공을 거두었지만 한계도 있었습니다. 예를 들어, Oyente는 간단한 계약에서도 많은 오탐을 발생시켰고, Slither의 패턴 기반 탐지기는 여러 오탐을 생성할 수 있습니다. 더욱이 2023년 한 연구에 따르면, 악용 가능한 계약 버그의 80% 이상 (특히 복잡한 "비즈니스 로직" 버그)이 현재 도구들로는 발견되지 못했다는 사실은 더 강력한 검증 기술의 필요성을 강조합니다.
완전 검증의 가능성과 과제: 이론적으로, 형식 검증은 모든 상태에 대한 불변성을 철저히 확인함으로써 버그가 없음을 증명할 수 있습니다. Certora Prover나 이더리움 재단의 KEVM 프레임워크와 같은 도구들은 스마트 계약을 형식 명세에 대해 수학적으로 검증하는 것을 목표로 합니다. 예를 들어, Certora의 "자동화된 수학적 감사기"는 명세 언어 (CVL)를 사용하여 사용자가 정의한 규칙을 증명하거나 반증합니다. 그러나 실제로는 실제 계약에서 속성을 완전히 증명하는 것은 종종 불가능하거나 매우 노동 집약적입니다. 코드를 검증을 위해 단순화된 형태로 다시 작성해야 할 수도 있고, 맞춤형 명세를 작성해야 하며, 루프와 복잡한 산술은 수동으로 경계를 설정하거나 추상화해야 할 수 있으며, SMT 솔버는 복잡한 로직에서 자주 시간 초과됩니다. Trail of Bits 엔지니어들이 언급했듯이, *“버그가 없음을 증명하는 것은 일반적으로 중 요하지 않은 코드베이스에서는 달성 불가능”*하며, 이를 달성하기 위해서는 사용자의 많은 개입과 전문 지식이 필요합니다. 이 때문에 형식 검증 도구는 전통적으로 전체 계약을 종단 간으로 검증하기보다는 토큰의 불변성이나 합의 알고리즘 검증과 같은 중요한 코드 조각에 드물게 사용되어 왔습니다.
Foundry의 퍼즈 테스트와 불변성 테스트
최근 몇 년 동안, 속성 기반 테스트가 완전한 형식 증명의 실용적인 대안으로 부상했습니다. 인기 있는 이더리움 개발 프레임워크인 Foundry는 퍼즈 테스트와 불변성 테스트를 내장 지원합니다. 이 기술들은 테스트 커버리지를 크게 향상시키며 경량 형식 검증으로 볼 수 있습니다. Foundry의 퍼즈 테스트는 지정된 속성을 위반하려는 다수의 입력을 자동으로 생성하며, 불변성 테스트는 이를 상태 변경 작업의 시퀀스로 확장합니다:
-
퍼즈 테스트: 특정 입력에 대한 단위 테스트를 작성하는 대신, 개발자는 모든 입력에 대해 유지되어야 하는 속성이나 불변성을 지정합니다. 그러면 Foundry는 수백 또는 수천 개의 무작위 입력을 생성하여 함수를 테스트하고 속성이 항상 유지되는지 확인합니다. 이는 개발자가 수동으로 테스트할 생각을 하지 못할 수 있는 엣지 케이스를 잡는 데 도움이 됩니다. 예를 들어, 퍼즈 테스트는 함수의 반환 값이 항상 음수 가 아니거나 특정 사후 조건이 입력에 관계없이 참임을 주장할 수 있습니다. Foundry의 엔진은 지능적인 휴리스틱을 사용합니다. 함수 시그니처를 분석하고 엣지 케이스 값 (0, max uint 등)을 도입하여 속성을 깨뜨릴 가능성이 있는 코너 케이스를 공략합니다. 만약 단언문이 실패하면, Foundry는 속성을 위반하는 반례 입력을 보고합니다.
-
불변성 테스트: Foundry의 불변성 테스트 (또는 상태 기반 퍼징)는 한 걸음 더 나아가 여러 함수 호출과 상태 전환을 순차적으로 실행합니다. 개발자는 계약의 수명 주기 동안 항상 참이어야 하는 불변성 함수를 작성합니다 (예: 총자산 = 사용자 잔액의 합). 그러면 Foundry는 무작위 호출 시퀀스 (무작위 입력 포함)를 생성하여 가능한 많은 사용 시나리오를 시뮬레이션하고, 주기적으로 불변성 조건이 계속 참인지 확인합니다. 이는 특정 작업 시퀀스 후에만 나타나는 복잡한 버그를 발견할 수 있습니다. 본질적으로, 불변성 테스트는 계약의 상태 공간을 더 철저히 탐색하여 유효한 트랜잭션 시퀀스가 명시된 속성을 위반할 수 없음을 보장합니다.
Foundry가 중요한 이유: Foundry는 이러한 고급 테스트 기술을 쉽게 사용할 수 있도록 만들었습니다. 퍼징 및 불변성 기능은 개발자 워크플로우에 내재되어 있어 특별한 하네스나 외부 도구가 필요 없으며, 테스트는 단위 테스트와 함께 솔리디티로 작성됩니다. Rust 기반 엔진 덕분에 Foundry는 수천 개의 테스트를 신속하게 (병렬 처리하여) 실행하고 모든 불변성 위반에 대한 상세한 실패 추적을 제공할 수 있습니다. 개발자들은 Foundry의 퍼저가 사용하기 쉽고 성능이 뛰어나며, 반복 횟수를 설정하거나 입력을 제한하기 위한 가정을 추가하는 등 약간의 구성만 필요하다고 보고합니다. Foundry 문서의 간단한 예는 divide(a,b) 함수에 대한 퍼즈 테스트로, vm.assume(b != 0)을 사용하여 사소한 유효하지 않은 입력을 피하고 result * b <= a와 같은 수학적 사후 조건을 단언합니다. 이러한 테스트를 수천 개의 무작위 (a,b) 쌍으로 실행함으로써 Foundry는 수동 추론으로는 찾기 어려운 엣지 케이스 (예: 오버플로우 경계)를 신속하게 발견할 수 있습니다.
비교: Foundry의 접근 방식은 커뮤니티의 이전 작업을 기반으로 합니다. Trail of Bits의 Echidna 퍼저는 이더리움을 위한 초기 속성 기반 테스트 도구였습니다. Echidna도 마찬가지로 불리언을 반환하는 솔리디티 함수로 표현된 불변성 위반을 찾기 위해 무작위 트랜잭션을 생성합니다. 이는 "지능적인" 입력 생성 (커버리지 기반 퍼징 포함)으로 알려져 있으며 많은 버그를 찾는 데 사용되었습니다. 실제로 보안 연구원들은 Echidna의 엔진이 매우 효과적이라고 언급합니다. “Trail of Bits의 Echidna는 지능적인 난수 선택 덕분에 최고의 퍼저입니다” – 하지만 Foundry의 통합된 워크플로우는 개발자가 테스트를 더 간단하게 작성할 수 있게 해줍니다. 실제로 Foundry의 퍼즈 테스트는 종종 안전한 솔리디티 개발을 위한 새로운 **“최소한의 기본 요건”**으로 간주되며, 전통적인 단위 테스트를 보완합니다. 이는 무작위적이고 철저하지 않기 때문에 버그가 없음을 증명할 수는 없지만, 방대한 범위의 입력과 상태 조합을 커버함으로써 신뢰도를 크게 높입니다.