EIP-8205 출금 자격증명 사전 등록 (Withdrawal credentials preregistration)

Hegota 포함 제외 (DFI) · 합의·스테이킹 · 원문 상태 Draft · 2026-03-26 작성

검증자 키를 가진 쪽이 예치 전에 출금 자격증명을 미리 묶어 두는 장치를 더합니다. 합의층이 예치 처리 때 이를 확인해 위임 스테이킹에서 남이 먼저 예치해 출금 주소를 가로채는 공격을 막습니다.

왜 필요한가

이더리움에서 검증자의 신분은 BLS 키입니다. 어떤 공개키로 처음 들어온 예치가 그 검증자의 출금 자격증명(withdrawal credentials)을 영구히 정합니다. 솔로 스테이커처럼 돈과 키를 한 사람이 쥐고 있으면 문제가 없습니다. 하지만 리퀴드 스테이킹이나 스테이킹 대행처럼 돈을 대는 쪽과 키를 만드는 쪽이 다른 위임 스테이킹에서는, 키를 가진 운영자가 자금 제공자의 예치보다 먼저 자기 출금 주소로 예치를 넣을 수 있습니다. 자금 제공자는 자신이 원하는 출금 자격증명이 지켜진다는 온체인 보장을 받지 못합니다. 원문은 스테이킹된 ETH의 최소 3분의 1이 이런 위임 구조를 거친다고 적습니다. 지금은 프로토콜마다 담보 기반 사전 예치나 보호자 위원회 같은 방어 장치를 따로 만들어 감사하고 유지합니다. 알려진 실제 피해는 없지만 새로 들어오는 사업자는 같은 장치를 갖추기 전까지 기본적으로 취약합니다.

무엇이 바뀌나

지금

EIP-8205 이후

누구에게 영향이 있나

심층 설명

실행층 요청 형식과 시스템 컨트랙트

사전 등록 요청은 EIP-7685 요청 타입 PREREGISTRATION_REQUEST_TYPE(값 TBD)으로, pubkey(Bytes48), withdrawal_credentials(Bytes32), signature(Bytes96) 세 필드를 이어 붙인 176바이트 레코드입니다. request_data는 꺼낸 레코드를 큐 순서대로 평탄하게 이어 붙인 값입니다. 컨트랙트 주소 PREREGISTRATION_REQUEST_PREDEPLOY_ADDRESS는 아직 TBD입니다. 컨트랙트에는 세 가지 경로가 있습니다. SYSTEM_ADDRESS(0xfffffffffffffffffffffffffffffffffffffffe)가 호출하면 이번 블록 분량의 요청을 큐에서 꺼냅니다. 그 밖의 호출자가 176바이트를 보내면 add_preregistration_request()가 수수료를 확인하고(check_fee) 블록 카운트를 1 올리고 요청을 큐에 넣은 뒤 pubkey ++ withdrawal_credentials ++ signature를 토픽 없는 익명 로그(LOG0)로 그대로 남깁니다. 입력 길이가 0이면 현재 수수료를 돌려주는 조회이며 값을 보내면 revert해야 합니다. 그 외 길이는 모두 revert입니다. 큐는 요청 하나당 저장 슬롯 6개를 쓰고 저장 슬롯 0~3은 excess, count, 큐 head, 큐 tail이며 큐 본문은 슬롯 4부터 시작합니다. 블록당 꺼내는 최대치는 MAX_PREREGISTRATION_REQUESTS_PER_BLOCK = 4, 목표치는 TARGET_PREREGISTRATION_REQUESTS_PER_BLOCK = 1입니다. 최대치는 꺼내는 수만 제한하고 한 블록 안에서 컨트랙트가 받아 주는 제출 수는 제한하지 않습니다.

수수료 계산과 큐 비활성화 경로

수수료는 EIP-7002, EIP-7251과 같은 방식입니다. get_fee()는 저장된 excess 값만 읽어 fake_exponential(MIN_PREREGISTRATION_REQUEST_FEE, excess, PREREGISTRATION_REQUEST_FEE_UPDATE_FRACTION)을 계산합니다. 최소 수수료는 1 wei, 갱신 분수는 17이며 수수료는 대략 e**(excess / 17) wei에 가깝습니다. 블록 끝 시스템 콜이 excess를 max(0, previous_excess + count - TARGET_PREREGISTRATION_REQUESTS_PER_BLOCK)으로 갱신하므로, 블록 N의 제출은 블록 N + 1의 수수료부터 영향을 줍니다. 같은 블록 안의 제출은 모두 같은 최소 수수료를 냅니다. 초과 지불은 환불되지 않으므로 제출 전에 빈 calldata로 수수료를 조회해야 합니다. excess 슬롯에 EXCESS_INHIBITOR(2**256-1)가 들어 있으면 get_fee()가 revert해 요청이 막힙니다. 활성화 뒤 첫 시스템 콜이 이 값을 0으로 되돌립니다. 시스템 콜에 빈 calldata가 아닌 값이 오면 요청을 꺼낸 뒤 EXCESS_INHIBITOR를 다시 저장해 큐를 비활성화합니다. 이 EIP에서는 시스템 콜을 항상 빈 calldata로 하므로 이 경로는 닿을 수 없으며 컨트랙트를 은퇴시키거나 멈추는 미래 업그레이드용으로 남겨 둔 것입니다. 이 장치가 없으면 프로토콜이 시스템 콜을 멈춘 뒤에도 컨트랙트가 수수료와 요청을 계속 받아 비워지지 않는 큐에 쌓입니다. EIP-8282 빌더 요청 컨트랙트의 참조 구현도 같은 방식을 씁니다.

블록 끝 시스템 콜 규칙

FORK_BLOCK부터 모든 실행 블록의 처리 끝에 SYSTEM_ADDRESS가 PREREGISTRATION_REQUEST_PREDEPLOY_ADDRESS를 빈 calldata로 호출합니다. 이 호출은 dequeue_preregistration_requests()로 큐에서 요청을 꺼내고 큐가 비면 head와 tail을 0으로 되돌립니다. update_excess_preregistration_requests()로 excess를 갱신하고 reset_preregistration_requests_count()로 count를 0으로 돌립니다. 꺼낸 요청은 반환된 순서 그대로 EIP-7685 요청 목록에 들어가야 합니다. 시스템 콜에는 전용 가스 한도 30_000_000이 있고 여기서 쓴 가스는 블록 가스 사용량에 넣지 않으며 블록 가스 한도 검사에서도 제외합니다. EIP-1559 수수료 소각 의미를 따르지 않고 값 전송도 없습니다. 해당 주소에 코드가 없거나 호출이 실패하거나 오류를 돌려주면 그 블록은 무효로 처리해야 합니다. 이 조건들은 EIP-7002와 같은 고려입니다.

합의층 저장 구조와 사전 등록 처리

합의층에는 PreregistrationRequest(pubkey, withdrawal_credentials, signature), 서명 대상인 ValidatorPreregistration(pubkey, withdrawal_credentials), 저장용 StoredPreregistration(pubkey, withdrawal_credentials, expiry_slot) 컨테이너가 새로 생깁니다. ExecutionRequests에 preregistrations 필드, BeaconState에 validator_preregistrations 필드가 추가됩니다. 페이로드당 요청 수는 MAX_PREREGISTRATION_REQUESTS_PER_PAYLOAD = 4를 넘을 수 없습니다. 요청은 EIP-7732에 따라 parent_execution_requests에서 예치 요청 다음에 적용됩니다. process_preregistration_request()는 같은 pubkey에 활성 등록이 이미 있을 때, 그 pubkey의 검증자가 이미 있을 때, is_pending_validator()가 참인 유효한 대기 예치가 있을 때, 활성 등록 수가 PREREGISTRATIONS_LIMIT(2**19 = 524,288)에 이르렀을 때 조용히 거부합니다. 통과하면 DOMAIN_PREREGISTRATION(0x11000000)과 genesis_validators_root로 도메인을 만들어 BLS 서명을 검증합니다. 이 도메인은 GENESIS_FORK_VERSION을 쓰므로 한 번의 서명이 과거와 미래의 모든 포크에서 유효하고 체인 구분은 genesis_validators_root가 맡습니다. DOMAIN_DEPOSIT을 재사용하지 않은 이유는 그 도메인에 genesis_validators_root가 없어 체인 간 재전송이 가능하기 때문입니다. 검증을 통과하면 expiry_slot = state.slot + PREREGISTRATION_EXPIRY_SLOTS(2**18 = 262,144 슬롯)로 저장하며 만료된 기존 레코드가 있으면 그 자리에 덮어씁니다. 클라이언트는 선형 탐색 대신 pubkey 보조 인덱스를 두는 것이 권장됩니다.

예치 처리 변경과 불일치 예치의 영구 거부

EIP-6110의 process_deposit_request()가 바뀝니다. 예치의 pubkey에 활성 등록이 있을 때만 검사가 붙습니다. 출금 자격증명이 등록과 다르면 예치를 조용히 거부합니다. 자격증명이 같아도 is_valid_deposit_signature()가 거짓이면 거부하고 등록은 그대로 둡니다. 둘 다 통과하면 remove_stored_preregistration()으로 등록을 지우고 PendingDeposit을 추가합니다. 같은 pubkey에 유효한 신규 검증자 대기 예치와 활성 등록은 공존할 수 없습니다. 이것이 핵심 불변식입니다. 서명이 무효한 PendingDeposit은 등록보다 먼저 대기열에 들어간 경우 공존할 수 있지만 일반 처리에서 무시되며 apply_pending_deposit은 바꿀 필요가 없습니다. 빌더 예치는 별도 요청 타입과 process_builder_deposit_request를 쓰므로 이 검사와 무관합니다. 거부된 예치의 ETH는 출금 기능이 없는 예치 컨트랙트에 남아 영구히 잃게 됩니다. 원문은 이를 의도된 설계로 설명합니다. 불일치 예치를 돌려주면 공격자가 비용 없이 예치, 거부, 회수를 반복할 수 있으므로, 영구 손실이 프런트러닝을 경제적으로 자멸시키는 장치가 됩니다. 사전 등록은 예치 데이터와 분리된 별도 BLS 서명 단계를 거치고 프로토콜이 EIP-4788로 등록을 확인한 뒤 예치하므로, 실수로 불일치가 날 위험은 낮다고 적습니다.

만료 판정과 EIP-7732 페이로드 슬롯

is_active_preregistration()은 state.latest_execution_payload_bid.slot이 저장된 expiry_slot보다 작은지 비교합니다. 요청이 적용되는 슬롯이 아니라 미처리 부모 입찰의 슬롯을 쓰는 이유는, EIP-7732에서 페이로드의 요청이 한 블록 뒤에 적용되기 때문입니다. 이렇게 하면 예치는 그 페이로드가 만들어진 슬롯에서 활성이던 등록에 대해 검사됩니다. 비교는 엄격 부등호이므로 expiry_slot과 같은 슬롯의 페이로드는 더 이상 보호받지 않습니다. process_preregistration_expiry()는 에포크 처리에서 만료된 레코드를 지우지만 순수한 가비지 컬렉션입니다. 그 시점이 등록의 강제 여부나 새 요청의 승인 여부에 영향을 주지 않으며 만료 레코드는 다음 정리 전까지 남아 있다가 활성 용량 검사를 거쳐 새 등록으로 덮어쓰일 수 있습니다. 절대 만료 슬롯은 저장 시점에 한 번 계산되므로 뒤에 PREREGISTRATION_EXPIRY_SLOTS가 바뀌어도 기존 기한은 바뀌지 않습니다. 또한 실행층에서 수명 파라미터나 슬롯 시간, SLOTS_PER_EPOCH를 모르고도 같은 창을 검증할 수 있습니다. 검증 컨트랙트가 EIP-4788로 expiry_slot을 증명하고 현재 EIP-7843 SLOTNUM이 그보다 작기를 요구하면 예치가 원자적으로 보호되거나 되돌려집니다. 만료는 최종성에 의존하지 않아 최종성이 멈춘 동안에도 진행됩니다. 쓰이지 않고 만료된 등록은 같은 서명 메시지를 다시 제출해 다시 세울 수 있습니다.

보안 고려와 파라미터 근거

서명의 영구성: 사전 등록 서명은 포크와 무관하고 암호학적으로 만료되지 않습니다. 온체인 레코드에만 262,144슬롯(12초 슬롯 기준 약 36일) 창이 있으며 서명 메시지를 가진 누구든 만료 뒤 다시 제출해 레코드를 되살릴 수 있습니다. 운영자는 선택한 자격증명이 의도한 출금 흐름에서 지원되는지 확인해야 합니다. 빌더 예치: 사전 등록은 검증자 예치에만 적용되고 EIP-8282 빌더 예치는 검사도 소모도 하지 않습니다. 0xB0 자격증명을 사전 등록해도 빌더가 등록되거나 보호되지 않으며 검증자 예치 경로로 0xB0을 넣으면 현재 출금할 수 없는 검증자가 생깁니다. 경쟁 조건: 등록이 처리되기 전에 공격자가 예치하면 first-deposit-wins로 검증자가 생기고 등록은 거부됩니다. 같은 페이로드에 둘이 함께 있어도 예치가 먼저 처리되어 결과는 같습니다. 그래서 프로토콜은 EIP-4788로 등록의 존재와 만료 기한을 예치와 원자적으로 확인해야 하고 별도 트랜잭션으로 확인하면 포함이 최종 확정될 때까지 기다려야 합니다. 상태 크기: 활성 레코드 최대 약 46 MB(88바이트 × 524,288)에, 다음 정리를 기다리는 만료 레코드가 최대 MAX_PREREGISTRATION_REQUESTS_PER_PAYLOAD × (SLOTS_PER_EPOCH - 1) = 124개 더해집니다. 블록당 1건 목표 속도로는 2**19를 채우는 데 약 72.8일이 걸려 36일 만료 창을 넘기므로, 정상 상태에서 활성 수는 약 2**18 근처(약 23 MB)에 머물고 포화시키려면 목표 초과 속도가 필요해 수수료가 지수적으로 오릅니다. 블록당 n건(n > 1)이 지속되면 excess가 블록마다 n - 1씩 늘어 약 17 × ln(10^18) / (n - 1) 블록 뒤 수수료가 1 ETH를 넘습니다.

현재 상태와 코어 개발자 콜 결정

이 EIP는 Draft 상태이며 2026-03-26에 만들어졌습니다. 시스템 컨트랙트 주소와 요청 타입 값, 런타임 바이트코드, 배포 트랜잭션, 테스트 벡터, 참조 구현이 모두 TBD이고 참조 구현과 함께 제공될 예정입니다. 합의층 변경도 원문에서 '스케치'로 적혀 있어 내용이 바뀔 수 있습니다. 코어 개발자 콜 기록을 보면 09.03 ACDC #186에서 논의됐지만 결정은 없었습니다. 10.15 ACDC #189에서는 결정이 다음 콜로 미뤄졌고 다음 콜 스코핑 세션을 이 안건으로 열어 5분 타임박스로 재논의하기로 했습니다. 10.29 ACDC #190에서 EIP-8205(예치 프런트러닝 수정)는 Hegota 후보에서 제외(DFI)됐습니다. 문제 자체는 인정했고 양자내성(PQ) 예치 컨트랙트 재설계에서 해결하기로 약속했습니다.

EIP 원문 · ← EIPs