EIP-2565 ModExp 가스 비용 재조정 (ModExp Gas Cost)

최종 확정 · 수수료·가스 · 원문 상태 Final · 2020-03-20 작성

ModExp 프리컴파일(주소 0x05)의 가스 비용 계산식을 실제 연산 비용에 맞게 다시 정의해 모듈러 지수 연산을 쓰는 암호 기능의 수수료를 낮춥니다.

왜 필요한가

모듈러 지수 연산(modular exponentiation)은 큰 수를 거듭제곱한 뒤 다른 수로 나눈 나머지를 구하는 계산입니다. 전자 서명, 검증 가능한 지연 함수(VDF), 영지식 증명(SNARK), 누산기(accumulator) 같은 여러 암호 기술이 이 계산 위에서 돌아갑니다. 이더리움은 이 계산을 빠르게 처리하도록 ModExp라는 내장 기능(프리컴파일)을 제공하는데, 벤치마크 결과 실제 걸리는 시간에 비해 수수료(가스)가 지나치게 비싸게 매겨져 있다는 사실이 드러났습니다. 기존 가스 공식이 계산의 어려움을 제대로 추정하지 못했기 때문입니다. 그 결과 이런 암호 기능을 이더리움 위에서 쓰기가 비효율적이고 비쌌습니다. 이 EIP는 실제 연산 비용을 더 정확히 반영하는 새 가스 계산식을 정의해 비용을 낮춥니다. 그 덕분에 보안 강화와 더 강한 난수 생성(VDF) 같은 활용이 현실적으로 가능해집니다.

무엇이 바뀌나

지금

EIP-2565 이후

누구에게 영향이 있나

심층 설명

바뀌는 것은 가스 산정식뿐입니다

이 EIP는 주소 0x0000000000000000000000000000000000000005에 있는 ModExp 프리컴파일의 가스 비용 계산 방법만 바꿉니다. 적용 시점은 FORK_BLOCK_NUMBER부터입니다. 프리컴파일의 입출력 인터페이스와 내부 산술 알고리즘에는 변화가 없어서 기존 테스트 벡터를 그대로 재사용할 수 있습니다. 새 비용은 곱셈 복잡도의 근사값에, 지수 연산을 수행하는 데 필요한 반복 횟수의 근사값을 곱하는 구조입니다. ModExp 프리컴파일 자체는 EIP-198이 도입했고 이 EIP는 그것을 전제로 합니다(requires: 198).

곱셈 복잡도: 8바이트 워드 수의 제곱

calculate_multiplication_complexity는 base_length와 modulus_length 중 큰 값을 max_length로 두고 이를 8로 나눠 올림한 값을 words로 삼은 뒤 words의 제곱(words**2)을 반환합니다. 8로 나누는 것은 다중 정밀도 산술에서 큰 수가 8바이트 단위 조각(limb)으로 쪼개져 처리되는 현실을 반영한 것입니다. EIP-198의 공식은 카라츠바(Karatsuba) 곱셈의 난이도를 근사하려 했지만 벤치마크 결과 이 새 공식이 실제 실행 시간에 더 잘 들어맞았습니다.

반복 횟수: 지수의 비트 길이 기반

calculate_iteration_count는 지수의 길이와 값에 따라 세 갈래로 나뉩니다. exponent_length가 32바이트 이하이고 exponent가 0이면 0, 32바이트 이하이면 exponent.bit_length() - 1, 32바이트를 넘으면 8 * (exponent_length - 32)에 지수 하위 256비트((exponent & (2**256 - 1)))의 bit_length() - 1을 더한 값입니다. 최종 결과는 max(iteration_count, 1)로 최소 1을 보장합니다. 이 식은 bigint 라이브러리들이 큰 지수에 쓰는 이진 지수 알고리즘의 반복 횟수를 근사합니다.

최종 가스식: 나누는 값 3과 최소 200

최종 가스는 max(200, floor(multiplication_complexity * iteration_count / 3))입니다. 나누는 값인 GQUADDIVISOR를 3으로 정하면 ModExp의 비용(초당 가스 기준)이 ECRecover 같은 다른 프리컴파일보다 여전히 높게 유지됩니다. 즉 싸게 만들되 다른 프리컴파일보다 과소 책정되지는 않게 맞춘 것입니다. 최소값 200은 아주 작은 입력에 대해 가격이 지나치게 낮아져 악용되는 것을 막는 장치입니다.

EIP-198 공식과 무엇이 다른가

EIP-198의 복잡도 함수는 x = max(length_of_MODULUS, length_of_BASE)에 대해 x가 64 이하이면 x**2, 1024 이하이면 x**2 // 4 + 96 * x - 3072, 그보다 크면 x**2 // 16 + 480 * x - 199680을 쓰는 구간별 식이었습니다. 실행 시간과 비교한 회귀 분석에서 이 EIP의 새 함수가 더 잘 맞았습니다. 새 함수의 회귀선이 테스트 벡터 점들을 가로지르는 모양이 나오는데, 이는 법(modulus)이 짝수인지 홀수인지에 따라 실행 시간이 달라지기 때문입니다.

테스트 벡터로 본 비용 변화

기존 테스트 벡터를 그대로 쓰되 가격만 갱신됩니다. 작은 입력은 최소값에 수렴합니다. modexp_nagydani_1_square는 204에서 200으로, modexp_nagydani_1_pow0x10001은 3,276에서 341로 줄어듭니다. 중간 크기인 modexp_nagydani_3_pow0x10001은 30,310에서 5,461로, 가장 큰 modexp_nagydani_5_pow0x10001은 285,900에서 87,381로 줄어듭니다. 전반적으로 큰 지수를 쓰는 사례일수록 절감 폭이 큽니다.

보안 고려와 현재 상태

가장 큰 보안 고려 사항은 ModExp 연산이 실제 계산 시간에 비해 너무 싸져서 서비스 거부(DoS) 공격의 통로가 되는 것입니다. GQUADDIVISOR를 3으로 맞춰 다른 프리컴파일보다 초당 가스 비용을 높게 유지하고 최소 200 가스를 두는 것이 이에 대한 대비입니다. 이 EIP의 상태는 Final로, 확정된 명세입니다. Geth 구현(go-ethereum PR 21607)과 파이썬 참조 구현이 공개되어 있습니다.

EIP 원문 · ← EIPs