
[Xangle Digest]
※해당 컨텐츠는 8월 9일 외부에서 기발간 된 컨텐츠입니다. 컨텐츠에 대한 추가적인 주의사항은 본문 하단에서 확인해주세요.
목차
계정 추상화 배경
계정 추상화는 어떻게 하면 가능할까?
ERC-4337 계정 추상화는 어떻게 동작할까?
EOA 계정과 Smart Contract
Problem1 : Smart Contract can’t generate Transaction.
Problem 2: Contract Deploy
맺으며
본 글은 CURG에서 기획한 계정 추상화(Account Abstraction) 시리즈 첫 번째로, 계정 추상화 기술의 동기와 현재 표준이 된 ERC-4337에 대해 개괄적으로 다루고, 이에 대한 구현 방식과 기술적 세부 내용을 살펴본다. 다음 편에서는 가스비 대납 솔루션 Paymaster와 가스비 최적화를 위한 Aggregator를 살펴보고, 실제 계정 추상화를 구현한 프로젝트를 리뷰할 예정이다.
계정 추상화 배경
이더리움은 상태 전이 시스템(State Transition System)이며, 상태 전이 요청은 계정을 중심으로 이루어진다. 계정의 분실은 상태 전이 요청(트랜잭션)을 할 수 없게 되며, 계정 정보(Mnemonic or Private Key)의 보관은 이더리움 환경에서 매우 중요하다. Vitalik은 자신의 블로그에 2019년 소셜 복구 기능을 제공하는 지갑 테스트에 관한 경험과 그에 대한 교훈을 작성하였다. 교훈은 다음과 같다. “비밀 공유 기반 오프체인 소셜 복구는 매우 취약하며, 복구를 위한 별도의 앱은 분실이 쉽고, 별도의 중앙 집중식 커뮤니케이션 채널은 여러 종류의 문제가 발생할 수 있다. 대신 guardians를 추가하는 방식은 이더리움 주소를 제공해야 하고, ERC-4337 계정 추상화 지갑을 사용하는 스마트 컨트랙트에 의하여 복구가 되어야 한다. guardians는 이더리움 지갑을 잃어버리지 않으면 되는데, 이는 이미 다른 이유로 신경 쓰고 있는 부분이다.”
계정 추상화 지갑 사용은 계정 복구와 같은 UX(User Experience)의 변화를 가능하여지도록 한다. 블록체인 사용에 있어서 여전히 많은 불편함이 존재하고, 이는 사용자들이 블록체인 기술을 사용하면서 쉽게 적응하지 못하도록 하고 있다. 계정 추상화는 무엇이며, 어떤 사용 사례가 이 아이디어의 동기가 되었을까? 다음은 계정 추상화 아이디어의 동기가 된 UseCase들이다.
- Multisig
- Cryptography other than ECDSA
- Privacy solutions
- On-chain DEXes
Multisig
출처: Coindesk.com (작성: Colin Harper)
Multisig(다중 서명) 지갑이나 Smart Contract 지갑(예, 소셜 복구)들은 거래 수수료를 지불하기 위해 소량의 이더리움을 저장하는 별도의 계정(EOA)이 필요하며, 시간이 지남에 따라 해당 계정이 이더리움을 재충전해야 하는 불편한 구조로 되어 있다. 다른 계정에 의하여 거래 수수료를 대납 될 수 있다면, 별도의 계정에 이더리움을 재충전해야 하는 불편함을 개선할 수 있다.
Cryptography other than ECDSA
출처: Medium @Level Up Coding- ECDSA — How to programmatically sign a transaction
이더리움에서는 전자서명에 ECDSA를 사용하며, 이외의 암호화 알고리즘을 사용하기 어렵다. ECDSA 이외의 암호화, Schnorr 서명, BLS 서명, 기타 곡선 및 Lamport/Winternitz와 같은 양자 증명 알고리즘을 모두 구현할 필요가 있을 수 있다.
Privacy Solutions
프라이버시 솔루션은 증명 검증이 인증 로직으로 이동하고 수수료 지급 조건이 되기 때문에 더 이상 “릴레이어(Relayer)”를 필요로 하지 않을 것이다.
On-chain DEXes
온체인 탈중앙 거래소 및 유사 시스템에서는 많은 사용자가 동일한 차익거래 기회를 얻기 위해 여러 트랜잭션을 시도하는 경우가 많다. 현재는 실패한 트랜잭션이 체인에 게시되기 때문에 비효율적이지만, 추상화를 사용하면 이론적으로 실패한 트랜잭션이 체인에 전혀 포함되지 않도록 할 수 있어 가스 효율성을 개선할 수 있다.
계정 추상화는 어떻게 하면 가능할까?
계정 추상화를 설명하는 가장 쉬운 방법은 기술의 접근 체계를 살펴보고 각 체계의 결함이 어디에 있는지 이해하는 것이다. 계정 추상화를 위한 기술의 발전 역사를 살펴보자.
Scheme 1 : Naive total abstraction
사용자는 일반적인 메커니즘을 사용해 가스비를 지불하지 않고도 ENTRY_POINT라고 하는 새로운 특수 주소(예: 2**160–1로 설정)에서 모든 계정으로 트랜잭션을 전송할 수 있다. 시사점은 이러한 트랜잭션의 “target”은 발신자의 계정이며, 계정 코드는 해당 데이터로 트랜잭션을 처리하고 원하는 작업을 수행한다는 의미이다.
채굴자는 거래를 완전히 처리하기 전에 계정 잔액을 확인하고, 거래를 완전히 처리한 후 계정 잔액을 확인하며, 차액이 충분한 수수료인지 확인하는 등 간단한 필터를 사용해 수락할 트랜잭션을 결정한다. 사용자는 요구되는 어떤 인증 단계를 거친 후, 수수료 지급을 위해 트랜잭션에 send(Coinbase(), fee)를 포함하기만 하면 된다.
이 접근 방식은 간단하고 최대한 유연하지만 두 가지 큰 문제가 있다.
- 마이너가 트랜잭션을 수락해야 하는지 여부를 알기 전에 모든 트랜잭션을 완전히 실행해야 한다는 것이다. DAO 소프트포크 시도에서 보았듯이, 이는 높은 수준의 DoS 취약성을 초래하는 나쁜 아이디어이다.
- 네트워크의 노드들은 트랜잭션을 전파할지 여부를 판단하는데 더 큰 어려움을 겪게 되는데, 트랜잭션을 실행하여 채굴자에게 보상을 지급할 것 같더라도 하나의 트랜잭션이 다른 모든 트랜잭션의 수수료 지급 속성을 무효로 할 수 있기 때문이다. 따라서 네트워크 수준의 DoS 위험은 훨씬 더 크다.
Scheme 2 : Signature abstraction only
“지갑 계정”이라고 하는 세 번째 계정 타입을 생성한다. 지갑 계정은 컨트랙트와 비슷하지만 두 가지 코드가 있는데: (i) 인증 코드와 (ii) 실행 코드다. ENTRY_POINT에서 지갑 계정으로 호출하는 과정은 두 단계로 이루어진다: (i) 전체 트랜잭션을 입력으로 사용하여 검증 코드를 실행하고 출력이 0이 아닌지 확인한 다음 (ii) 실행 코드를 정상적으로 실행한다. 검증 코드 실행은 외부 호출(사전 컴파일 제외), 컨트랙트 저장소 또는 어떤 것에 ‘쓰기’ 기능을 사용할 수 없으며, pure 함수여야 한다. 또한 검증 코드 실행에는 400,000의 고정 가스 제한이 있다.
이 방식은 마이너와 네트워크 노드가 해야 하는 모든 것이 이전과 동일하므로 기본적으로 보안 위험이 없다. 유일한 차이점은 마이너가 ECDSA 검증(순수 함수) 대신 제한된 환경 내에서 일부 EVM 코드(또 다른 순수 함수)를 실행해야 한다는 것이다. 그러나 이 방식은 추상화의 이점 중 일부만 제공한다.
Scheme 3 : 제안된 체계(EIP-2938)
다음은 대부분의 위험 없이 완전한 추상화의 많은 이점을 제공하는 구체적인 계획이다. 첫째, EVM 레벨이 변경된다:
- 우리는 두 가지 유형의 계정, 즉 EOA와 컨트랙트 계정(CA)이 있다는 속성을 유지하며, 후자는 하나의 코드만 가지고 있다.
- 서명이 (0, 0)인 트랜잭션은 tx.origin(따라서 top-level sender)이 ENTRY_POINT 주소(= 2**160–1)인 트랜잭션으로 취급된다.
- PAYGAS라는 opcode 추가. (PAYGAS는 스택에서 하나의 인수(gasprice)를 가져오고, 잔액에서 tx.startgas * gasprice를 차감하고, 트랜잭션 실행이 끝날 때 remaining_gas * gasprice를 환불해야 함을 기록하고, 트랜잭션이 실패할 경우 opcode를 호출한 시점으로 revert 하도록 기록). 최상위 호출 프레임에서 사용되지 않으면 아무것도 하지 않으며(예시: msg.sender == ENTRY_POINT), opcode가 이미 활성화된 상태에서 사용되면 아무것도 하지 않는다. 성공하면 스택에 1을 푸시하고 실패하면 0을 푸시.
이제 마이너(채굴자)와 네트워크 노드, 정책을 살펴보면:
- 채굴자나 네트워크 노드는 트랜잭션을 수신하면 최상위 컨트랙트의 코드가 require(msg.sender == ENTRY_POINT)로 시작하는지 확인한다(정확한 EVM 바이트 시퀀스 보류 중). 그렇지 않다면 트랜잭션을 거부한다.
- 다음 세 가지 상황 중 하나에 도달할 때까지 코드를 실행한다:
1. 호출(프리컴파일 제외)이 이루어졌거나 외부 상태 읽기 연산자 코드(BALANCE, EXTCODE*) 또는 환경 연산자 코드(TIMESTAMP, DIFFICULTY, NUMBER, BLOCKHASH, COINBASE, GASLIMIT)가 사용되었다면, 이 경우 트랜잭션이 거부된다.
2. 400,000 가스가 사용되었다면, 이 경우 트랜잭션이 거부된다.
3. PAYGAS opcode가 사용된다면, 이 경우 opcode에 주어진 가스 가격이 충분한지 여부에 따라 거래를 수락하거나 거부한다. - 마이너와 네트워크 노드는 각 계정에 대해 두 개 이상의 트랜잭션을 중계하지 않는다. 계정 추상화는 부수적으로 하나의 트랜잭션 내에서 여러 작업을 수행할 수 있는 계정을 지원하기 때문에 사용성에 미치는 영향은 낮다.
이 방식은 이더리움의 프로토콜 레벨에서 수정해야 하는 큰 문제를 가지고 있다.
Scheme 4 : 제안된 체계(EIP-4337)
이 방식은 이더리움 프로토콜의 변경 없이 분산 방식으로 네이티브 스마트 계약 지갑을 지원하기 위한 첫 번째 단계이다. 스마트 계약 지갑을 지원하기 위해 합의 레이어를 수정하는 대신, 일반적인 트랜잭션 gossip 프로토콜과 별도로 새로운 시스템이 추가된다. 이 상위 레벨 시스템은 사용자 작업(UserOperation)이라는 새로운 개체를 기반으로 구축되며, 사용자의 작업과 관련된 서명과 함께 패키징된다. 이 UserOperation 개체는 전용 메모리 풀로 브로드캐스트되어 검증자들이 이를 “번들 트랜잭션”으로 수집할 수 있게 된다. 번들 트랜잭션은 많은 개별 UserOperation의 연속을 나타내며, 일반 트랜잭션과 마찬가지로 이더리움 블록에 포함될 수 있으며, 검증자들은 유사한 수수료 최대화 선택 모델을 사용하여 이를 선택한다.
EIP-4337에 따라 지갑의 작동 방식도 변경된다. 각 지갑이 일반적이지만 복잡한 안전성 로직을 재구현하는 대신, 해당 기능은 “Entry Point”이라고 알려진 global 지갑 컨트랙트에 외부로 아웃소싱된다. 이를 통해 수수료 지불 및 EVM 코드 실행과 같은 작업을 처리하여 지갑 개발자가 훌륭한 사용자 경험을 제공하는 데 집중할 수 있게 된다.
참고로, EIP-4337 Entry Point 컨트랙트는 2023년 3월 1일에 Ethereum Mainnet에 배포되었고, 최근까지 지속해서 업데이트 되고 있다.
그외 다른 접근
EIP-2771 : 메타 트랜잭션을 사용한 계정 추상화
EIP-2771는 이더리움 프로토콜을 변경하지 않고도 제삼자가 사용자의 가스 비용을 지불할 수 있는 메타 트랜잭션 개념을 도입한다. 사용자에 의해 서명된 트랜잭션은 Forwarder 계약에 전송된다. 포워더는 트랜잭션이 유효한지 검증한 후 가스 릴레이로 전송하는 신뢰할 수 있는 개체이다. 이는 오프체인에서 이루어지며, 가스를 지불할 필요가 없어진다. 가스 릴레이는 트랜잭션을 수신자 컨트랙트에 전달하고, 이를 실행하기 위해 필요한 가스를 이더리움에 지불한다. 포워더가 수신자에게 알려진 신뢰할 수 있는 개체인 경우에만 트랜잭션이 실행된다. 이 모델은 개발자가 사용자를 위해 가스 비용이 없는 트랜잭션을 구현하기 쉽게 만든다.
EIP-3074 : EOA 업그레이드를 통해 계정 추상화 지원
EIP-3074은 이더리움의 외부 소유 계정을 업데이트하여 스마트 컨트랙트에게 제어 권한을 위임할 수 있도록 하는 것을 목표로 한다. 이는 스마트 컨트랙트 로직이 외부 소유 계정에서 시작된 트랜잭션을 승인할 수 있다는 것을 의미한다. 이를 통해 가스 후원 및 일괄 처리된 트랜잭션과 같은 기능을 구현할 수 있다. 이를 위해 EVM에는 AUTH와 AUTHCALL이라는 두 개의 새로운 OP코드가 추가되어야 한다. EIP-3074를 통해 스마트 계약 지갑의 이점을 계약 없이도 활용할 수 있게 되며, 대신 “인보커(invoker)”라고 하는 특정 유형의 상태를 가지지 않고 신뢰할 수 있는 업그레이드 불가능한 계약이 트랜잭션을 처리한다.
ERC-4337 계정 추상화는 어떻게 동작할까?
출처: notes.ethereum.org -The road to account abstraction
사용자는 기존의 Transaction 대신, UserOperation 객체를 전송하고, 전송된 UserOperation 객체는 Bundler의 mempool에 등록된다. Bundler는 높은 수수료를 지불하는 UserOperation 객체를 우선 선택하여, 이것을 모아 “Bundle Transaction”을 만들고, 이를 이더리움 블록에 넣기 위해 Transaction을 전송한다. 위 그림은 UserOperation 객체가 처리되는 과정을 그림으로 나타낸 것이다.
Bundler는 Bundle Transaction에 포함된 UserOperation 객체가 실패하는 경우, 가스 낭비가 발생하기 때문에, 사전에 검증할 필요가 있다. Entry point contract는 UserOperation 객체들을 검증하고 실행하는 역할을 하며 Verification 단계와 Execution 단계가 필수로 구현되어 있어야 한다. Verification 단계에서는 UserOperation의 wallet contract가 존재하지 않는 경우 새로 생성하고, wallet contract에서 ValidateUserOp를 호출하여 UserOperation과 필요한 수수료를 전달한다. 그리고 wallet contract에서는 UserOperation의 서명과 작업이 유효하다면 wallet contract는 수수료를 지불해야 한다. Execution 단계는 wallet contract에서 op execution 함수를 호출하여 calldata를 실행하고, 남은 가스비를 환불해 준다. 위 그림은 Bundler의 계정에서 Entry point contract를 통해 UserOperation 객체를 처리하는 과정을 나타낸 그림이다. UserOperation 객체가 처리 완료되면, 사용자가 원하는 요청이 수행된 것이다.
계정 추상화 지갑의 이점
- 다른 사람의 가스를 지불하거나, 다른 사람이 귀하의 가스를 지불
- 이더가 아닌 ERC-20 토큰으로 수수료 지불
- 자신만의 유연한 보안 규칙 정의
- 키를 분실한 경우 계정 복구
- 신뢰할 수 있는 장치 또는 개인 간에 계정 보안 공유
- 배치 트랜잭션(예: 한 번에 스왑 승인 및 실행)
- dapp 및 지갑 개발자가 사용자 경험을 혁신할 수 있는 더 많은 기회
ERC-4337기반의 계정 추상화 지갑의 이점은 블록체인 UX의 많은 변화를 가져올 수 있을 것으로 기대된다. 다른 사람이 대신 가스비를 지불 해주거나, 내가 가진 ERC-20토큰으로 수수료를 지불 할 수 있다면, 가스비가 부족하여 이더를 구매하고 지갑에 옮기는 불편함이 없어질 것이다. 계정을 복구 할 수 있으면, 계정 분실에 대한 걱정을 덜어줄 수 있으며 블록체인 사용이 더 증가 할 수 있다. 배치 트랜잭션으로 여러 요청을 한번에 처리 할 수 있는 것 역시 가스비 감소와 사용 편의성이 커지는 장점이 있다. 하지만, 물론 장점만 있는 것은 아니다. 트랜잭션 처리에 있어서 기존에 사용하는 방식대비 UserOperation을 통해 처리하는 것에는 가스비가 증가하는 문제가 있고, 여전히 더 발전해나가야 하는 부분이 있다.
EOA 계정과 Smart Contract
현재 우리가 메타마스크에서 사용하는 계정은 EOA 계정이다. 즉, 패스워드같이 작동하는 개인 키를 사용하여 Transaction에 대해 서명하는 방식이다. 반면 Smart Contract는 Code로 동작하는 계정이다. 계정 내부에 Code를 가지고 있으며 Smart Contract를 사용하기 위해서는 먼저 Transaction을 통한 배포과정(Contract Creation)이 필요하고 이후 EOA 계정에서 해당 Smart Contract로 Transaction을 전송하여 Code를 실행시킬 수 있다. 즉, 정리하자면 다음과 같이 요약할 수 있다.
- Smart Contract는 Transaction을 만들 수 없다.
- Smart Contract는 배포 과정이 필요하다.
Problem1 : Smart Contract can’t generate Transaction.
위 그림은 이더리움의 3가지 Transaction을 나타낸 것이다.
첫 번째는 EOA에서 EOA로 보내는 Transaction. 직관적으로는 ETH를 송금하는 과정이다.
두 번째는 EOA에서 CA(Contract Account : Smart Contract를 계정으로 사용할 때의 용어)로 Transaction을 전송해 Code를 실행시키는 것이다. 예를 들면 우리가 Uniswap을 통해 Swap을 실행시키는 경우이다.
세 번째는 EOA에서 CA로 Transaction을 Code를 실행시켰는데 Code가 Internal Transaction을 생성하여 다른 CA의 Code를 실행시켜 주는 상황이다.
해당 그림을 통해 모든 Transaction의 첫 발생 주체는 EOA 이며, 이후 Internal Transaction의 형태로 CA가 CA로 Transaction을 생성할 수 있다는 것을 알 수 있다. 따라서 CA가 자체적으로 Tx을 생성할 수 없으므로, CA 의 코드를 실행시키기 위해서는 EOA 계정이 Tx을 생성해줘야 한다. ERC-4337에서는 이 문제를 해결하기 위해 UserOp와 Bundler의 개념을 추가한다.
Module : UserOp & Bundler
위 그림에서 3번째 Transaction을 아이디어로 가져왔다. 이제 Transaction을 생성하기 전에 상위 Level의 요청이 추가되었다. 새로운 방식에서는 Bundler 라는 Transaction을 생성시켜주는 새로운 주체를 설정한다. 이 주체는 요청이 들어오면 User’s CA로 Transaction을 발생시켜주는 역할을 하고 해당 요청을 UserOp라고 부른다.
정리하자면,
- UserOp : CA을 사용하는 User가 Bundler에게 Tx의 실행을 요청하는 요청 단위
- Bundler : CA을 대신해서 자신의 EOA계정으로 Tx을 실행시켜주는 주체
User는 EOA 계정을 보유하지 않아도 자신의 CA 계정으로 Tx을 생성할 수 있는 효과를 만들 수 있다. 아래는 실제 UserOp의 필드이다.
User의 입장에서는 완벽하지만, Bundler에게는 남은 문제가 존재한다. Bundler의 관점에서는 User가 요청하는 임의의 Contract에게 임의의 데이터를 전송해야 하는 것이다.
생각해보면 그 임의의 Contract 가 USDT 일 수 있고, 임의의 데이터가 approve 함수를 호출할 수 있다. 물론 시뮬레이션을 통해서 어느 정도 그러한 악의적인 UserOp를 거를 수 있지만 Transaction이 실제로 다르게 동작할 수 있으므로 잠재적 위협에서 안전하지 못하다.
Entry Point라는 신뢰 가능한 컨트랙트를 도입해서 이 문제를 해결하고자 한다.
Module : Entry Point
Entry Point는 이러한 문제점을 해결하기 위해서 나온 중앙의 싱글 톤 컨트랙트이다. 해당 Contract의 코드는 오픈소스로 공개되어 있으며 ERC-4337을 제안한 프로젝트 팀에서 배포되었다. [소스 확인]
중앙에 단 하나의 Contract 인 Entry Point를 배포했으므로, 이제 해당 Contract로 UserOp를 전송함으로써 Bundler가 임의의 Contract를 실행시키지 않아도 된다.
출처: Etherscan
간단하게 etherscan.io 에서 Entry Point 를 입력하면 해당 컨트랙트를 확인할 수 있다.
출처: Etherscan
모든 ERC-4337 를 사용한 UserOp는 해당 중앙 Entry Point를 사용하므로 얼마나 ERC-4337이 활성화되어있는지 쉽게 확인할 수도 있다. 들어가서 어떤 Tx이 오가는지 확인해보면 구조를 더 쉽게 이해할 수 있다.
Entry Point가 도입된 이후 동작 과정을 살펴보자.
- Bundler가 UserOp를 받으면 Tx을 생성하여 CA가 아닌 Entry Point로 UserOp를 전달한다.
- Entry Point에서 Contract Wallet로 UserOp가 전달된다.
- Contract Wallet에서 해당 UserOp가 실행된다.