ERC-4361 이더리움 계정으로 로그인(SIWE) (Sign-In with Ethereum)

Final · 서명·인증 · 2021-10-11 작성

이더리움 계정으로 웹사이트 같은 오프체인 서비스에 로그인하는 표준 메시지 형식과 검증 절차를 정합니다. 중앙 로그인 제공자 없이 지갑 서명만으로 세션을 만들 수 있습니다.

왜 필요한가

지금 일반 웹서비스에 로그인할 때는 대형 인터넷 기업이나 이메일 제공자 같은 중앙화된 신원 제공자(Identity Provider, IdP)를 거칩니다. 이들은 사용자의 식별자를 최종적으로 통제하고 사용자와 이해관계가 어긋나기도 합니다. 한편 이더리움 쪽에서는 이미 많은 서비스가 메시지 서명으로 계정을 인증해 쿠키 기반 세션을 만들고 있지만 메시지 형식이 서비스마다 달라 서로 호환되지 않았습니다. 지갑도 어떤 서명 요청이 로그인용인지 구분할 수 없어 알기 쉬운 화면을 보여 주기 어려웠습니다. 요청을 보낸 사이트와 메시지에 적힌 사이트가 다른 피싱 요청을 걸러 내기도 어려웠습니다. 이 표준은 로그인 메시지의 형식과 양쪽의 검증 절차를 하나로 정해 이 문제를 풉니다.

심층 설명

메시지 형식: 첫 줄과 필수 필드

메시지는 RFC 5234의 ABNF 문법에 반드시 맞아야 합니다. 첫 줄은 선택적 `scheme://` 뒤에 `domain`, 그리고 ` wants you to sign in with your Ethereum account:` 문구로 이루어집니다. 다음 줄에 `address`(0x 뒤 16진수 40자, EOA는 ERC-55 체크섬 대소문자 권장), 빈 줄, 선택적 `statement`, 빈 줄이 이어집니다. 그 뒤 `URI: `, `Version: `, `Chain ID: `, `Nonce: `, `Issued At: ` 다섯 줄은 필수이며 순서가 고정입니다. `version`은 이 명세에서 반드시 `1`입니다. `chain-id`는 세션이 묶이는 EIP-155 체인 ID이자 컨트랙트 계정을 찾을 네트워크입니다. `nonce`는 영숫자 8자 이상, 날짜 필드는 RFC 3339 형식입니다. 문자열 항목은 대소문자를 구분하므로 `Chain ID`를 `chain id`로 쓰면 형식 위반입니다.

선택 필드: 만료·유효 시작·요청 ID·리소스

필수 다섯 줄 뒤에 `Expiration Time: `(만료 시각), `Not Before: `(이 시각 전에는 무효), `Request ID: `(시스템별 요청 식별자), `Resources:`(사용자가 인증 과정에서 함께 확인받고 싶은 정보의 URI 목록)를 순서대로 붙일 수 있습니다. 각 resource는 RFC 3986 URI여야 하고 줄바꿈 뒤 `- `로 시작하는 한 줄씩 나열합니다. `scheme`을 생략하면 HTTPS로 간주합니다. `domain`은 RFC 3986 authority이므로 `example.com:3388`처럼 포트를 포함할 수 있습니다. 포트가 없으면 scheme의 기본 포트(HTTPS는 443)로 봅니다. `statement`는 사람이 읽는 ASCII 문장이며 줄바꿈 문자(0x0a)를 포함하면 안 됩니다. `resources`를 어떻게 해석할지는 이 명세의 범위 밖입니다.

서명 방식: EOA는 ERC-191, 컨트랙트 계정은 ERC-1271

의존 당사자는 메시지 앞에 `\x19Ethereum Signed Message:\n<메시지 길이>` 접두어를 붙여(ERC-191) 서명을 요청합니다. 외부 소유 계정(EOA)은 ERC-191의 검증 방법을 반드시 써야 합니다. 컨트랙트 계정은 ERC-1271 검증이 권장이며 다른 방식을 쓰면 구현자가 그 방법을 명확히 정의해야 합니다. ERC-1271 검증 때 검증 컨트랙트는 반드시 메시지의 `chain-id`가 가리키는 체인에서 찾아야 합니다. ERC-1271 구현은 순수 함수가 아니어도 되므로 같은 입력이라도 체인 상태에 따라 결과가 달라질 수 있습니다. 원문은 상태 변경 알림(웹훅)을 받아 관련 세션을 무효화하는 방식을 예로 듭니다. EIP-712 대신 ERC-191을 고른 이유는 지갑 지원이 이미 넓고 접두어만 붙이면 되어 구현이 단순한 데다 사람이 읽을 수 있는 메시지가 나오기 때문입니다.

서버 구현: 출처 지정, 검증, 세션

`domain`과 `scheme`은 서명 요청이 실제로 발생한 출처(origin)와 반드시 일치해야 합니다. 교차 출처 iframe 안에서 요청했다면 iframe이 상위 창을 사칭하지 못하도록 부모 창이 아닌 iframe의 출처를 적어야 합니다. 서명된 메시지를 받으면 ABNF 형식 준수를 확인하고 파싱한 값 중 `expiration-time`, `nonce` 등이 기대값과 맞는지 검사한 뒤 서명을 검증해야 합니다. 세션은 반드시 `address`에 묶어야 합니다. ENS 이름처럼 나중에 바뀔 수 있는 파생 데이터에 묶으면 안 됩니다. `resources`의 URI는 평문으로 보였을 때 사람이 읽기 쉬운 형태가 권장됩니다. 서명 전에 지갑은 서버에 적절한 `nonce`나 `resources`를 물어볼 수 있지만 이때 사용자 정보 노출을 최소화하는 조치가 권장됩니다.

지갑 구현: 출처 검증으로 피싱 차단

지갑은 메시지 전체가 ABNF에 맞는지 확인해야 합니다. `wants you to sign in with your Ethereum account` 문구가 들어 있는데 형식에 완전히 맞지 않는 ERC-191 서명 요청은 사용자에게 경고하는 것이 권장됩니다. 요청 출처와 메시지의 `scheme`·`domain` 비교가 핵심입니다. 권장 알고리즘에서는 `scheme`이 없으면 기본값(브라우저 지갑은 https)을 넣고 허용 목록에 없는 scheme이면 반드시 거부합니다. scheme이 출처와 다르면 거부(권장), `domain`의 host가 출처와 다르면 반드시 거부, 서브도메인이 다르면 거부(권장)합니다. 포트가 다르면 경고를 보입니다. 개발자 모드(출처가 localhost인 경우 등)에서는 거부 대신 경고로 완화할 수 있습니다. 출처는 브라우저 창이나 WalletConnect(ERC-1328) 세션처럼 신뢰할 수 있는 곳에서 읽어야 합니다.

지갑 화면: 반드시 보여 줄 필드

지갑은 서명 전에 `scheme`, `domain`, `address`, `statement`, `resources`가 있으면 기본으로 반드시 표시해야 합니다. 나머지 필드도 기본 화면이나 확장 화면으로 서명 전에 볼 수 있게 해야 합니다. 평문 메시지를 그대로 보여 주는 지갑은 사용자가 텍스트 영역 끝까지 스크롤한 뒤에만 서명하게 하는 것이 권장됩니다. ABNF 항목을 파싱해 전용 화면을 만들어도 되며 이때도 위 표시 규칙은 그대로 적용됩니다. 파싱이 끝난 뒤에는 언어별 번역을 UX 단계에서 할 수 있습니다. 의존 당사자나 지갑은 ENS 기본 이름(EIP-181), 아바타 등을 추가로 조회해 보여 줄 수 있습니다. 다만 조회 과정에서 주소가 제3자 서비스로 전달될 수 있으므로 사용자 프라이버시와 동의를 지키는 조치가 권장됩니다.

범위 밖 항목과 하위 호환

이 버전은 이더리움 주소 기반이 아닌 인증, 서버 리소스에 대한 권한 부여, `resources` URI의 해석, 도메인 바인딩과 nonce 생성의 구체적 방법, TLS 없는 프로토콜은 다루지 않습니다. 향후 과제로 분산 식별자(DID)와 검증 가능 자격 증명, 크로스체인, SIOPv2, EIP-712 지원, 버전 해석 규칙을 열어 두었습니다. JWT를 쓰지 않은 이유는 지갑이 지원하지 않고 keccak 해시가 IANA의 JOSE 알고리즘으로 등록되지 않았기 때문입니다. YAML 대신 ABNF를 쓴 이유는 문자 집합·순서·공백을 엄격히 정할 수 있기 때문입니다. 대부분의 지갑이 이미 ERC-191을 지원하므로 기존 서명 로그인에서 올라가기 쉽습니다. 약관 동의 문구·nonce·주소 포함 같은 요구사항은 기존 구현에서 모았습니다. 상태는 Final입니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs