EIP-8151 계정 코드로 제한하는 ecRecover (Account Code Restricted ecRecover)

Hegota 포함 검토 (CFI) · 보안·프라이버시 · 원문 상태 Draft · 2026-02-09 작성

서명에서 주소를 복원하는 ecRecover 프리컴파일이 복원된 계정의 코드를 확인합니다. 코드가 비어 있거나 EIP-7702 위임 표시일 때만 주소를 돌려주고, 그 외에는 서명 실패와 같은 빈 출력을 돌려줍니다.

왜 필요한가

일반 계정(EOA)은 EIP-7702를 디딤돌 삼아 양자내성(post-quantum) 서명 방식으로 옮겨 갈 수 있습니다. 옮긴 뒤에는 계정 코드가 EIP-7702 위임 형식과 달라지므로, EIP-3607 규칙에 따라 옛 ECDSA 개인키로 트랜잭션을 보내는 권한은 프로토콜 차원에서 막힙니다. 그런데 서명에서 주소를 복원하는 ecRecover 프리컴파일은 계정 상태를 보지 않습니다. 양자 컴퓨터로 옛 개인키를 알아낸 공격자가 ERC-20 permit(ERC-2612) 같은 컨트랙트 서명 기능을 거쳐 토큰을 옮길 수 있다는 뜻입니다. 이런 컨트랙트 대부분은 이미 배포되어 고칠 수 없습니다. 그래서 ecRecover 자체에 같은 제한을 넣어 이주를 마친 계정의 옛 키를 컨트랙트 인증에서도 쓸 수 없게 합니다.

무엇이 바뀌나

지금

EIP-8151 이후

누구에게 영향이 있나

심층 설명

바뀐 ecRecover 동작 순서

활성화 이후 주소 0x0000000000000000000000000000000000000001의 ecRecover는 다음 순서로 동작합니다. (1) 가스 3000을 소모하고 부족하면 out of gas로 중단합니다. (2) 입력 (hash, v, r, s)로 지금처럼 ECDSA 공개키를 복원하고 실패하면 빈 출력을 돌려줍니다. (3) 복원에 성공하면 EIP-8038 기준으로 코드 확인 비용을 계산합니다. 콜드인지 웜인지는 계정을 읽기 전에 accessed_addresses로 판단합니다. 이는 EIP-7928의 사전 상태 가스 검사입니다. recovered_address가 accessed_addresses에 없으면 COLD_ACCOUNT_ACCESS(3000), 있으면 WARM_ACCESS(100)이고 여기에 코드 조회 수수료로 WARM_ACCESS(100)를 하나 더합니다. 코드가 비어 있어도, 나중에 검사에서 거부되어도 이 수수료는 면제되지 않습니다. (4) 그 비용을 소모하고 부족하면 out of gas로 중단하며 이때는 계정 접근이 일어나지 않습니다. (5) recovered_address를 accessed_addresses에 넣고 계정 읽기를 기록한 뒤 원시 코드를 읽습니다. (6) 코드가 비어 있거나 정확히 EIP-7702 위임 표시이면 recovered_address를 32바이트로 왼쪽 패딩해 돌려줍니다. (7) 그 밖에는 빈 출력을 돌려줍니다. 복원 실패와 코드 검사 거부 모두 success = 1에 빈 출력입니다.

계정 코드 검사 조건

검사는 recovered_address의 상태에 저장된 원시 계정 코드를 그대로 봅니다. 위임 표시를 따라가 대상 컨트랙트의 코드를 읽지 않습니다. 존재하지 않는 계정은 빈 코드로 취급합니다. 허용 조건은 len(code) == 0 이거나, len(code) == 23 이면서 code[:3] == 0xef0100 인 경우뿐입니다. 즉 코드가 비어 있거나 정확히 0xef0100 || delegate_address 형태일 때만 복원된 주소를 돌려줍니다. 참조 구현의 상수는 DELEGATION_PREFIX = 0xef0100, DELEGATION_CODE_LEN = 23입니다. 양자내성 방식으로 이주해 코드가 다른 형태로 바뀐 계정은 이 조건을 통과하지 못합니다.

가스 비용

기본 가스 3000은 복원 전에 한 번 청구됩니다. 복원이 실패하면 여기서 끝나 상태 접근도 추가 비용도 없습니다. 복원에 성공하면 원시 코드를 읽어야 하므로 EXTCODESIZE, EXTCODECOPY와 같은 방식으로 EIP-8038의 COLD_ACCOUNT_ACCESS(3000) 또는 WARM_ACCESS(100)에 코드 조회용 WARM_ACCESS(100)를 더합니다. 코드 확인 비용은 콜드 3100, 웜 200이고 총비용은 콜드 6100, 웜 3200입니다. 계정은 검사가 빈 코드인지 거부 대상인지 알기 전에 이미 읽히므로 어느 결과든 수수료를 면제하지 않습니다. 이 비용은 주소가 웜 처리되거나 기록되거나 읽히기 전에 청구됩니다. 가스가 모자라면 out of gas로 중단되고 계정 접근은 추가되지 않습니다. 비용을 낸 뒤에야 주소가 accessed_addresses에 들어가며 바깥 범위가 되돌려지지 않는 한 이후 연산에서도 웜 상태로 남습니다.

블록 접근 목록(EIP-7928) 기록

EIP-7928이 활성화된 상태에서 코드 확인 비용을 내는 것은 recovered_address에 대한 계정 읽기입니다. 비용을 낸 뒤에만 읽기를 기록하며 out of gas가 난 코드 검사는 계정 접근을 남기지 않습니다. 읽는 대상은 recovered_address의 원시 코드이고 위임 대상은 불러오지 않습니다. 그 계정에 다른 변경이 없으면 변경 목록을 비운 채로 포함합니다. 코드 검사가 계정을 거부해도 읽기 기록은 남고 바깥 범위가 나중에 되돌려져도 남습니다. 되돌려진 범위는 accessed_addresses를 그 범위에 들어올 때의 내용으로 복원하며 이미 있던 접근은 그대로 유지됩니다. 프리컴파일은 읽기만 하므로 그 외 변화가 없는 계정은 변경 목록이 빈 상태입니다.

왜 프로토콜 수준에서, 왜 빈 출력인가

ERC-20 permit처럼 ecRecover에 기대는 컨트랙트 다수가 불변이라 복원된 계정의 코드를 확인하도록 고칠 수 없습니다. 프리컴파일을 바꾸면 재배포 없이 같은 제한이 자동으로 적용됩니다. 거부 결과를 빈 출력으로 둔 이유는 기존 동작과 맞추기 위해서입니다. 지금의 ecRecover는 v가 잘못되었거나 r, s가 범위를 벗어나거나 복원이 실패하면 RETURNDATASIZE == 0인 빈 출력을 돌려줍니다. 거부된 계정에 32바이트 0을 돌려주면 이 결과가 달라집니다. mload(returndatasize())를 인증된 주소와 비교하는 호출자는 그 주소가 0 주소일 때 32바이트 0을 받아들이게 되고 RETURNDATASIZE가 0이라서 서명을 거부하던 호출자도 같은 32바이트를 받아들이게 됩니다. 빈 출력은 거부된 계정을 복원 실패와 같은 경로에 둡니다. 실행 실패(success = 0)로 처리하는 방안도 배제했습니다. 저수준 staticcall로 ecRecover를 감싸고 require(success)를 거는 배포된 컨트랙트에서 잘못된 서명이 revert로 바뀌기 때문입니다.

하위 호환성과 고정 가스 호출

원시 코드가 비어 있지도, 정확히 EIP-7702 위임 표시도 아닌 주소에 대해 ecRecover는 이전에 복원된 주소를 돌려주던 자리에서 빈 출력을 돌려줍니다. 복원 실패는 여전히 빈 출력이고 비용도 3000 그대로입니다. 복원에 성공할 때마다 기본 3000 위에 콜드 3100 또는 웜 200이 더해져 총 6100 또는 3200이 됩니다. 가스 한도에 가깝게 ecRecover를 호출하던 트랜잭션은 활성화 후 out of gas가 날 수 있습니다. 특히 staticcall(3000, 1, ...)처럼 가스 3000을 고정해 넘기는 호출은 서명이 유효하더라도 복원에 성공하기만 하면 항상 out of gas가 나며 트랜잭션 전체 가스를 늘려도 해결되지 않습니다. EIP-7928이 활성화된 경우 비용을 낸 코드 검사는 복원된 주소를 블록 접근 목록에 기록하고 검사가 주소를 거부하거나 이후 범위가 되돌려져도 그 기록은 남습니다.

보안 고려 사항

코드 검사에서 거부된 계정은 잘못된 서명과 같은 빈 출력을 받으므로, 그 결과를 실패로 다루는 컨트랙트는 지금처럼 동작합니다. 복원 실패를 제대로 거부하지 않는 호출자는 잘못된 서명을 받아들일 수 있습니다. 이 EIP는 ecrecover 프리컴파일만 바꿉니다. 순수 Solidity 구현처럼 애플리케이션 코드에서 직접 ECDSA 복원을 하는 컨트랙트는 프리컴파일을 거치지 않으므로 계정 코드 제한을 받지 않습니다. 다른 체인, 다른 레이어, 또는 나중에 비동기로 ecrecover를 다시 실행하면서 (hash, v, r, s)만의 순수 함수라고 가정하는 시스템에서는 그 가정이 깨집니다. 복원된 계정의 현재 원시 코드를 읽기 때문입니다. L1 프리컴파일을 실행해 최종 결과를 얻는 L2 사기 증명(fault proof) 시스템은 L1과 L2 상태가 다를 때 틀릴 수 있습니다. 이 EIP를 채택하는 L2는 주소를 복원한 뒤 자기 원시 계정 코드와 트랜잭션 접근 상태로 코드 검사와 EIP-8038 접근 비용을 적용해야 합니다. 계정 상태와 무관한 복원이 필요한 시스템은 이 프리컴파일 대신 별도의 secp256k1 복원 구현을 써야 합니다.

현재 상태와 포크 논의

원문 상태는 Draft이며 2026-02-09에 만들어졌습니다. 명세, 가스 수치, 블록 접근 목록 규칙은 아직 바뀔 수 있습니다. 코어 개발자 콜에서는 09.16 AMA #15에서 논의만 있었고 결정은 없었습니다. 10.08 ACDE #247에서 EIP-8151(ecRecover 제한)과 EIP-8298(SETCODEFROM)을 한 쌍으로 묶어 헤고타(Hegota) 포크의 CFI(검토 대상)로 올렸습니다. 일반 계정이 ECDSA에 기대지 않고 양자내성으로 이주할 경로를 여는 조합입니다. 10.22 ACDE #248에서는 bogota-devnet-2 범위를 EIP-8253, EIP-4758, EIP-8298, EIP-8151 네 개로 확정하고 다음 주 Nethermind, Geth, ethrex 세 클라이언트로 출시하기로 했습니다. 헤고타 후보 EIP를 실제 네트워크에서 돌려보는 실험망입니다. 포크 최종 포함 결정은 콜 기록에 없습니다.

EIP 원문 · ← EIPs