EIP-4399 DIFFICULTY 옵코드를 PREVRANDAO로 교체 (Supplant DIFFICULTY opcode with PREVRANDAO)
최종 확정 · EVM·실행 · 원문 상태 Final · 2021-10-30 작성
작업증명 종료로 의미를 잃는 DIFFICULTY(0x44) 명령어가 비콘체인의 난수(RANDAO 믹스)를 반환하게 바꾸고 이름을 PREVRANDAO로 바꿉니다.
왜 필요한가
이더리움이 지분증명(PoS)으로 바뀌면(EIP-3675) 블록에 작업증명(PoW) 봉인이 없어지고 difficulty 필드는 0으로 고정됩니다. 그러면 채굴 난이도를 돌려주던 DIFFICULTY(0x44) 명령어는 의미를 잃고 돌려줄 올바른 값도 없어집니다. 그런데 기존 스마트 컨트랙트 가운데는 이 명령어의 반환값을 다른 값과 섞어 난수로 쓰는 패턴이 흔합니다. 이 EIP는 같은 번호의 명령어가 비콘체인이 쌓아 온 난수(RANDAO 출력)를 돌려주게 해서 기존 컨트랙트를 고치지 않아도 PoS 전환 뒤에 난수를 계속 얻을 수 있게 합니다. 아울러 반환값이 2의 64제곱보다 크면 PoS 블록이라는 점을 이용해 컨트랙트가 전환이 일어났는지 스스로 판별할 수도 있습니다.
무엇이 바뀌나
지금
- DIFFICULTY(0x44)는 블록의 채굴 난이도를 반환합니다
- 헤더의 mixHash 필드는 PoW 봉인의 값을 담습니다
- 컨트랙트는 난이도 값을 다른 값과 섞어 난수로 씁니다
- 컨트랙트가 PoS 전환 여부를 알기 어렵습니다
EIP-4399 이후
- 같은 번호의 PREVRANDAO(0x44)가 비콘체인 RANDAO 난수를 반환합니다
- mixHash에 이전 블록의 최신 RANDAO 믹스가 담기고 이름이 prevRandao로 바뀝니다
- 기존 컨트랙트가 코드 수정 없이 비콘체인 난수를 받습니다
- 반환값이 2**64보다 크면 PoS 블록임을 알 수 있습니다
누구에게 영향이 있나
- 스마트 컨트랙트 개발자: DIFFICULTY로 난수를 얻던 기존 컨트랙트는 코드를 고치지 않아도 비콘체인 난수를 받습니다. 다만 난수의 편향·예측 특성이 PoW 때와 다르므로 충분한 룩어헤드 같은 권장 사용법을 지켜야 합니다.
- 클라이언트 팀: 전환 블록부터 헤더의 mixHash에 이전 블록의 RANDAO 믹스를 기록하고 0x44 명령어가 이 값을 반환하도록 구현해야 합니다.
- 검증자(블록 제안자): 블록 제안 포기나 트랜잭션 검열로 난수에 슬롯당 1비트씩 영향을 줄 수 있어 난수를 쓰는 앱 입장에서 잠재적 편향 주체가 됩니다.
- 반환값 크기에 의존하던 앱: 반환값이 작은 수에서 256비트 전 범위의 값으로 바뀌어 오버플로나 언더플로 가능성이 생깁니다. 원문은 이것이 취약점으로 이어질 확률을 무시할 수준으로 봅니다.
심층 설명
블록 구조: mixHash에 RANDAO 믹스를 기록
TRANSITION_BLOCK(EIP-3675가 정의하는 PoS 전환 블록)부터 클라이언트는 블록 헤더의 13번째(0부터 셈) 필드인 mixHash에 이전 블록의 포스트 비콘 상태(post beacon state)가 가진 최신 RANDAO 믹스를 반드시(MUST) 기록합니다. RANDAO 출력을 헤더에 넣으면 헤더 데이터가 이미 EVM 컨텍스트에 있으므로 EVM 안에서 바로 읽을 수 있고 합의 레이어에서 추가 입력을 받지 않아도 블록만으로 실행 레이어를 온전히 실행할 수 있습니다. 다른 헤더 필드가 다른 블록과 같아지는 경우에도 블록 해시의 유일성에 기여합니다.
EVM: 0x44가 mixHash 값을 반환
TRANSITION_BLOCK부터 DIFFICULTY(0x44) 명령어는 mixHash 필드 값을 반드시(MUST) 반환합니다. 명령어의 가스 비용은 바뀌지 않습니다. 이후 mixHash 필드는 prevRandao로, DIFFICULTY(0x44) 옵코드는 PREVRANDAO(0x44)로 이름을 바꾸기를 권고(SHOULD)합니다. 새 옵코드를 만들지 않고 기존 번호를 재사용한 이유는, DIFFICULTY 반환값을 다른 값과 섞어 난수를 얻는 패턴이 기존 컨트랙트에 흔해서 PoS 전환을 하위 호환으로 만들기 위해서입니다.
왜 difficulty가 아니라 mixHash를 재사용하나
난수를 difficulty 필드에 넣지 않은 것은 숨은 포크초이스 버그를 피하기 위해서입니다. EIP-3675 이전 로직은 총난이도(total difficulty) 계산이 PoW 포크초이스의 기반이라 difficulty 값에 크게 의존합니다. 전환 뒤 difficulty를 0으로 두면 총난이도가 계속 커지는 데서 오는 버그 표면이 줄어들고 난수가 difficulty 필드에 들어갔다면 남아 있는 총난이도 계산이 오버플로에 취약해졌을 것입니다. 반대로 mixHash는 전환 시점에 폐기되어 0 바이트 배열로 채워질 필드였으므로, 재사용하면 블록당 32바이트를 아끼고 업그레이드로 폐기되는 필드 하나를 되살리는 효과가 있습니다.
2**64 임계값으로 PoS 블록 판별
반환값이 2**64보다 크면 해당 트랜잭션이 PoS 블록에서 실행 중이라고 판단할 수 있습니다. RANDAO 값이 0에서 2**64 사이에 들어가 PoW 난이도 값과 섞일 확률은 극히 낮습니다. 메인넷 난이도가 당시 약 2**54 수준이라 여유가 부족해 보일 수 있지만 이 임계값이 불안전해지려면 해시레이트가 1000배 늘어야 하며 합의 업그레이드 전에 그런 일은 일어날 수 없다고 봅니다. 디컴파일러 같은 도구도 트랜잭션이 포함된 블록 데이터가 있으면 이 방법으로 명령어의 새 의미를 구분할 수 있습니다.
편향 가능성: 제안자의 1비트 영향력
비콘체인 RANDAO에서 블록 제안자는 슬롯마다 1비트의 영향력을 가집니다. 제안자가 제안 보상과 트랜잭션 수수료를 포기하고 블록을 제안하지 않으면 그 슬롯의 RANDAO 믹스 갱신을 막을 수 있습니다. 연속 n개 슬롯의 제안자가 담합하면 n비트가 되지만 영향은 시간상 제한적이어서 이후 정직한 RANDAO 공개 하나면 편향이 풀립니다. 또 PREVRANDAO의 의미상 제안자가 주사위를 굴리는 트랜잭션을 검열해 다음 블록에 포함되게 만들면, 자신이 미리 아는 RANDAO 믹스를 쓰도록 강제할 수 있고 이때 기회비용은 무시할 만큼 작습니다.
예측 가능성과 개발자 권장 사항
과거 난수는 100% 예측 가능하고 미래 난수도 짧은 범위에서는 제한적으로 예측할 수 있습니다. 미래 난수에 영향을 주는 입력으로 누적 난수(에포크 N 마지막 슬롯의 RANDAO 믹스가 에포크 N+MIN_SEED_LOOKAHEAD+1의 슬롯별 제안자를 정하는 주 입력), 활성 검증자 수, 유효 잔액, 우발적 제안 누락(메인넷 기준 약 1%)이 있습니다. 원문은 충분한 룩어헤드를 권합니다. 에포크 K 말에 응모를 마감하고 K+N+ε 슬롯의 믹스로 추첨하되, N은 4 에포크 이상, ε는 2~4 슬롯처럼 작게 둡니다. ε의 시간 여유는 MIN_SEED_LOOKAHEAD가 정하며 메인넷에서 약 6분입니다. 4 에포크 이상 기다리면 응모 마감 시점에 에포크 N+1의 제안자 집합이 알려져 있지 않고 그 기간에 제안 누락이 일어날 확률이 사실상 100%라 예측이 더 어려워집니다.
하위 호환성과 상태
이 EIP는 블록이나 슬롯 번호가 아니라 TRANSITION_BLOCK을 기준으로 삼아 EIP-3675의 PoS 전환과 단단히 묶여 있고 채택한다면 반드시(MUST) EIP-3675와 같은 시점에 적용해야 합니다. 영향을 받을 수 있는 앱은 두 부류입니다. 반환값을 PoW 난이도 자체로 쓰는 앱은 PoS 전환 자체로 이미 영향을 받으므로 이 EIP가 추가로 깨뜨리는 것은 없습니다. 반환값이 256비트 전체 범위에 비해 작은 수라는 데 의존하는 앱은 오버플로나 언더플로가 생길 수 있지만 이것이 보안 취약점으로 이어질 확률은 무시할 수 있는 수준으로 봅니다. 이 EIP의 상태는 Final로 명세가 확정되었습니다.