ecrecover는 32바이트 digest와 v·r·s로 Ethereum address를 복구하지만 서명이 누구의 어떤 행위를 어느 chain·contract에서 승인했는지 판단하지 않습니다. EIP-2는 transaction signature의 high-s를 무효화했지만 ecrecover precompile은 과거 서명 호환을 위해 high-s도 계속 받습니다. Contract는 s가 secp256k1 order의 절반 이하인지, v가 허용 형식인지, 복구 주소가 zero가 아닌지 확인해야 합니다. 여기에 EIP-191·712 prefix, chainId, verifying contract, action parameters, nonce와 deadline을 digest에 묶어 replay 범위를 제한해야 합니다.
같은 의미의 ECDSA 서명이 둘일 수 있습니다
secp256k1 ECDSA에서 유효한 (r,s)에 대해 (r,n-s)와 반대 recovery parity도 같은 공개키를 복구할 수 있습니다. 서명 bytes가 다르므로 signature hash를 사용한 unique key나 ‘이 bytes는 아직 안 썼다’는 mapping은 우회될 수 있습니다. 권한의 핵심 replay key는 message nonce와 signer여야 합니다.
EIP-2 Homestead 규칙은 Ethereum transaction의 s가 n/2보다 크면 invalid로 만들었지만 ecrecover precompile은 변경하지 않았습니다. Old Bitcoin signatures 같은 용도를 보존하기 위해서입니다. 따라서 wallet이 항상 canonical signature를 만든다는 가정 대신 contract 경계에서 low-s를 검사합니다.
ecrecover는 암호학적 복구 함수이지 승인 정책 함수가 아닙니다. 복구된 주소 앞뒤의 메시지 문맥은 계약이 직접 검증해야 합니다.
JOBCOIN 해설

그림은 이 주제의 공통 개념을 단순화한 설명입니다. 아래 항목에서 이 글의 구체적인 조건과 예외를 함께 읽어보세요.
- 같은 의미의 ECDSA 서명이 둘일 수 있습니다
secp256k1 ECDSA에서 유효한 (r,s)에 대해 (r,n-s)와 반대 recovery parity도 같은 공개키를 복구할 수 있습니다.
- v·r·s와 실패 결과를 모두 검증합니다
Solidity ecrecover는 오류 시 address(0)를 반환합니다.
- Permit 예제로 digest 범위를 점검합니다
Owner가 spender에게 100 token allowance 를 주는 off-chain signature를 만든다고 하겠습니다.
AI로 제작한 개념도 · 실제 가격·거래 내역·통계가 아닙니다.
v·r·s와 실패 결과를 모두 검증합니다
Solidity ecrecover는 오류 시 address(0)를 반환합니다. Zero address가 role mapping에서 허용되거나 signer 입력과 우연히 같아지는 로직이면 실패가 승인으로 바뀔 수 있어 recovered != address(0)을 먼저 확인합니다. r·s range와 lower-half s, v가 27·28인지 또는 library가 0·1을 정규화하는지 명시합니다.
EIP-2098 compact signature처럼 parity가 s의 최상위 bit에 들어가는 형식도 있으므로 64바이트·65바이트 parser를 섞지 않습니다. Transaction의 EIP-155 v 값과 message signature v를 같은 방식으로 해석하면 chain id 산식이 잘못 적용될 수 있습니다.
| 검사 | 막는 문제 | 누락 시 결과 |
|---|---|---|
| low-s·v range | ECDSA 가변 표현 | 다른 bytes 재사용 |
| zero address | 복구 실패 | 실패를 signer로 오인 |
| domain prefix | 다른 서명 종류 혼용 | transaction·message 혼동 |
| chain·contract | cross-domain replay | 다른 배포에서 재사용 |
| nonce·deadline | 시간·횟수 replay | 같은 승인 반복 실행 |
Permit 예제로 digest 범위를 점검합니다
Owner가 spender에게 100 token allowance를 주는 off-chain signature를 만든다고 하겠습니다. Digest에는 owner, spender, value뿐 아니라 현재 nonce와 deadline, domain의 chainId와 verifyingContract가 들어가야 합니다. Contract는 recovered signer가 owner인지 확인하고 nonce를 소비한 뒤 allowance를 갱신합니다.
Value만 서명하면 공격자는 다른 spender나 contract에 서명을 붙일 수 있습니다. Nonce를 포함해도 state를 증가시키기 전에 token hook 같은 외부 호출을 하면 재진입으로 같은 nonce가 재사용될 수 있습니다. Signature 검증, nonce effect, interaction 순서를 하나의 state transition으로 봅니다.
- 서명 scheme과 prefix byte를 고정합니다.
- Typed fields와 type hash를 test vector로 검산합니다.
- s lower-half·v 형식·zero address를 검사합니다.
- Chain id·verifying contract·nonce·deadline을 포함합니다.
- Nonce를 외부 호출 전에 소비하고 event를 남깁니다.
EIP-191은 의도하지 않은 Ethereum transaction을 막습니다
EIP-191 signed data는 0x19 뒤 version byte와 version-specific data, payload 구조를 사용합니다. 첫 0x19는 서명 데이터가 유효한 RLP Ethereum transaction이 되지 않게 합니다. Version 0은 intended validator address를 넣고 version 0x45는 personal_sign 계열 prefix에 사용됩니다.
Prefix가 있다는 사실만으로 application domain이 충분하지는 않습니다. 두 contract가 같은 payload와 validator 규칙을 공유하면 replay가 가능할 수 있습니다. EIP-712 domain separator 또는 protocol 고유 domain을 사용하고 proxy upgrade에서 verifying contract와 chain fork 처리 정책을 문서화합니다.
서명 bytes보다 승인 상태를 감사합니다
운영 로그에는 raw signature 전체보다 signer, digest, nonce, action과 transaction hash를 연결해 남깁니다. Signature를 unique ID로 쓰지 않고 message digest·signer·nonce의 소비 상태를 기준으로 중복 실행을 막습니다. 개인키나 미공개 승인 payload는 로그에 남기지 않습니다.
Test vector는 정상 low-s, 대응 high-s, 잘못된 v, zero recovery, 다른 chain id·contract, 만료와 nonce 재사용을 포함합니다. Frontend가 보여 준 문구와 contract가 hash한 bytes가 같은지 독립 구현으로 비교해야 합니다.
자주 묻는 질문
Ethereum transaction이 low-s이면 contract 서명도 자동 low-s인가요?
아닙니다. EIP-2의 transaction 유효성 규칙과 ecrecover precompile 입력 검사는 다르므로 contract가 직접 확인해야 합니다.
ecrecover가 zero address를 반환하면 signer가 0인 건가요?
복구 실패일 수 있습니다. 유효 signer로 취급하지 말고 명시적으로 거부해야 합니다.
Nonce만 넣으면 replay가 모두 막히나요?
같은 contract 내 반복은 줄이지만 다른 chain·contract 재사용을 막으려면 domain도 digest에 포함해야 합니다.
더 깊이 읽기
본문에서 다룬 개념과 확인 절차를 다음 글에서 이어서 살펴보세요.



