지난 글에서는 이더리움 상태가 계정 단위로 관리된다고 봤습니다.

이번에는 그 계정을 두 종류로 나눠서 봅니다.

이더리움 계정
├── EOA
└── Contract Account

처음에는 아주 단순하게 구분해도 됩니다.

EOA: 사람이 개인키로 관리하고, 트랜잭션을 시작할 수 있는 계정
Contract Account: 코드로 동작하고, 호출되었을 때 실행되는 계정

이 차이를 이해하면 트랜잭션, 스마트 컨트랙트, EVM이 조금 더 자연스럽게 이어집니다.

EOA는 무엇인가

EOA는 Externally-Owned Account의 줄임말입니다.

말이 어렵지만 핵심은 간단합니다.

EOA
= 개인키를 가진 사람이 제어하는 계정

사용자는 개인키로 트랜잭션에 서명합니다.

그 서명은 “이 계정의 소유자가 이 트랜잭션을 승인했다”는 증거로 쓰입니다.

사용자
-> 개인키로 트랜잭션 서명
-> 네트워크는 서명을 검증
-> 이 EOA에서 나온 트랜잭션인지 확인

그래서 개인키를 가진다는 것은 단순히 비밀번호를 아는 것보다 더 강한 의미입니다.

개인키는 해당 계정의 자산 이동과 트랜잭션 실행을 승인할 수 있는 권한입니다.

서명은 어떻게 검증하나

여기서 한 가지 질문이 생깁니다.

노드는 개인키를 모르는데,
이 트랜잭션이 계정 소유자가 서명한 것인지 어떻게 알까?

핵심은 서명에서 주소를 확인할 수 있다는 점입니다.

지갑 앱은 트랜잭션 내용을 개인키로 서명합니다.

트랜잭션 내용
+ 개인키로 만든 서명
= 서명된 트랜잭션

노드는 서명된 트랜잭션을 받습니다.

노드는 개인키를 받지 않습니다. 개인키를 알 필요도 없습니다.

대신 트랜잭션 내용과 서명 값을 이용해서, 이 서명이 어떤 주소의 계정에서 나온 것인지 확인합니다.

서명된 트랜잭션
-> 서명 검증
-> 서명한 계정 주소 확인

확인된 주소가 이 트랜잭션을 보낸 계정이면 유효한 트랜잭션으로 볼 수 있습니다.

서명에서 확인한 주소
=
트랜잭션을 보낸 주소

조금 더 정확히는 이더리움이 ECDSA라는 서명 방식을 사용합니다. 지금은 이름을 외우는 것보다 구조를 이해하는 것이 중요합니다.

개인키: 서명을 만들 수 있음
주소: 서명이 어느 계정에서 나온 것인지 확인하는 기준
노드: 개인키 없이 서명을 검증함

그래서 지갑 앱은 개인키를 노드에 보내지 않습니다.

지갑 앱은 로컬에서 서명하고, 노드에는 서명된 트랜잭션만 보냅니다.

주소는 어떻게 만들어지나

Ethereum.org의 accounts 문서는 EOA가 공개키와 개인키 쌍으로 구성된다고 설명합니다.

여기서 “누가 만드는가”를 먼저 잡아야 합니다.

보통 사용자가 직접 수학 계산을 하는 것은 아닙니다. 지갑 앱이 개인키를 만들고, 그 개인키에서 공개키와 주소를 계산합니다.

사용자
-> 지갑 앱에서 새 계정 생성

지갑 앱
-> 개인키 생성
-> 개인키에서 공개키 계산
-> 공개키에서 이더리움 주소 계산

사용자
-> 주소를 받아서 사용
-> 개인키 또는 시드 구문은 안전하게 보관

흐름을 더 단순하게 쓰면 이렇습니다.

지갑 앱이 개인키를 만든다
-> 지갑 앱이 공개키를 계산한다
-> 지갑 앱이 공개키를 Keccak-256으로 해시한다
-> 지갑 앱이 마지막 20바이트를 주소로 사용한다
-> 앞에 0x를 붙여 이더리움 주소로 보여준다

네트워크가 개인키를 만들어주는 것은 아닙니다.

네트워크는 나중에 트랜잭션 서명을 검증할 때, 그 서명이 해당 주소의 계정에서 나온 것인지 확인합니다.

그래서 이더리움 주소는 보통 이렇게 생겼습니다.

0x5e97870f263700f46aa00d967821199b9bc5a120

0x 뒤에는 40개의 16진수 문자가 옵니다.

20바이트
= 160비트
= 40개의 16진수 문자

앞의 0x는 “이 값은 16진수로 읽어라”는 표시입니다.

개인키를 숨겨야 하는 이유

개인키에서 공개키와 주소를 만들 수 있습니다.

하지만 주소만 보고 개인키를 알아낼 수는 없습니다.

개인키 -> 공개키 -> 주소
주소 -> 개인키는 불가능에 가깝다

그래서 주소는 다른 사람에게 알려줘도 됩니다.

하지만 개인키는 절대 공개하면 안 됩니다.

개인키를 가진 사람은 그 계정의 트랜잭션에 서명할 수 있습니다.

즉, 개인키를 잃어버리면 계정을 움직일 수 없고, 개인키가 유출되면 다른 사람이 계정을 움직일 수 있습니다.

Contract Account는 무엇인가

Contract Account는 스마트 컨트랙트가 배포되면서 만들어지는 계정입니다.

EOA와 가장 큰 차이는 제어 방식입니다.

EOA
-> 개인키로 제어

Contract Account
-> 코드로 제어

Contract Account에는 개인키가 없습니다.

대신 코드와 저장소가 있습니다.

Contract Account
├── address
├── balance
├── code
└── storage

코드는 이 계정이 호출되었을 때 실행되는 로직입니다.

storage는 그 코드가 읽고 쓸 수 있는 저장 공간입니다. 정확히는 storage라는 공간 자체가 움직이는 것이 아니라, storage 안에 저장된 값이 바뀝니다.

컨트랙트 주소는 언제 생기나

컨트랙트를 배포하면 Contract Account가 만들어지고, 그 계정에도 주소가 생깁니다.

Ethereum.org의 accounts 문서는 컨트랙트 주소가 보통 컨트랙트를 만든 주소와 그 주소의 nonce에서 나온다고 설명합니다.

처음에는 이렇게 이해하면 충분합니다.

EOA가 컨트랙트 배포 트랜잭션을 보냄
-> 네트워크가 컨트랙트 코드를 저장
-> Contract Account 생성
-> 컨트랙트 주소 생성

즉 컨트랙트 주소는 사람이 개인키를 만들어서 얻는 주소가 아닙니다.

배포 트랜잭션의 결과로 생긴 계정의 주소입니다.

Contract Account는 왜 스스로 시작하지 못하나

EOA는 트랜잭션을 시작할 수 있습니다.

Contract Account는 스스로 트랜잭션을 시작하지 못합니다.

이 말은 처음에는 이상하게 들릴 수 있습니다.

컨트랙트도 코드가 있으니 혼자 실행될 수 있을 것 같기 때문입니다.

하지만 이더리움에서 코드는 누군가 호출했을 때 실행됩니다.

외부 호출 없음
-> 컨트랙트 코드 실행 안 됨

EOA가 트랜잭션 보냄
-> 컨트랙트 호출됨
-> 컨트랙트 코드 실행

Ethereum.org도 Contract Account가 트랜잭션을 받은 것에 대한 응답으로만 메시지를 보낼 수 있다고 설명합니다.

즉 컨트랙트는 먼저 움직이는 주체라기보다, 호출을 받으면 정해진 코드대로 반응하는 계정입니다.

ETH 전송과 컨트랙트 호출

EOA가 트랜잭션을 보낼 때 대상은 두 가지가 될 수 있습니다.

EOA -> EOA
EOA -> Contract Account

EOA에서 EOA로 보내는 트랜잭션은 보통 ETH 전송입니다.

A의 EOA
-> B의 EOA
-> 1 ETH 전송

이 경우에는 주로 잔액이 바뀝니다.

A balance 감소
B balance 증가

EOA에서 Contract Account로 보내는 트랜잭션은 컨트랙트 호출이 될 수 있습니다.

A의 EOA
-> Contract Account
-> input 데이터 전달
-> 컨트랙트 코드 실행

이 경우에는 단순히 잔액만 바뀌는 것이 아닐 수 있습니다.

컨트랙트 코드가 실행되면서 storage 안의 값이 바뀌거나, 이벤트 로그가 남거나, 다른 컨트랙트를 호출할 수 있습니다.

Contract Account 코드 실행
-> storage 안의 값 변경
-> log 생성
-> 다른 Contract Account 호출

그래서 컨트랙트 호출은 단순 송금보다 더 넓은 의미를 가집니다.

버튼을 누르면 무슨 일이 생기나

사용자 입장에서는 지갑에서 버튼을 누르는 것처럼 보입니다.

하지만 내부 흐름은 이렇게 볼 수 있습니다.

사용자가 dapp에서 버튼 클릭
-> 지갑이 트랜잭션 내용 표시
-> 사용자가 승인
-> EOA 개인키로 트랜잭션 서명
-> 트랜잭션이 네트워크에 전파
-> 블록에 포함
-> Contract Account 코드 실행
-> 상태 변경

여기서 지갑은 계정을 사용하게 해주는 도구입니다.

EOA는 실제로 서명 권한을 가진 계정입니다.

Contract Account는 호출된 뒤 코드가 실행되는 계정입니다.

둘의 차이 정리

EOA와 Contract Account의 차이를 표처럼 정리하면 이렇습니다.

EOA
- 개인키로 제어된다
- 트랜잭션을 시작할 수 있다
- 보통 사용자의 지갑 주소로 보인다
- 코드는 없다

Contract Account
- 코드로 제어된다
- 스스로 트랜잭션을 시작하지 못한다
- 컨트랙트 배포로 만들어진다
- code와 storage를 가진다

가장 중요한 차이는 이것입니다.

EOA는 서명해서 시작한다.
Contract Account는 호출되면 실행된다.

다음은 EVM이다

여기까지 보면 자연스럽게 다음 질문이 생깁니다.

Contract Account의 코드는 어디에서 실행되는가?

그 답이 EVM입니다.

EVM은 Ethereum Virtual Machine의 줄임말입니다.

컨트랙트 계정의 코드는 EVM 위에서 실행됩니다. 다음 글에서는 EVM을 아주 기초적인 수준에서 봅니다.

정리

오늘 기준으로는 이렇게 정리합니다.

EOA는 개인키로 서명해서 트랜잭션을 시작하는 계정이고, Contract Account는 코드와 저장소를 가진 계정으로 호출되었을 때 실행된다.

참고 자료