본문 바로가기
솔리디티

EIP-4337 이해하기

by gun_poo 2023. 3. 22.

eip 4337은 합의 규칙을수정하지 않고 이더리움 메인넷에 계정 추상화 기능을 추가하기 위한 사양이다.

 

계정 추상화란 무엇인가?

계정 추상화란 사용자가 eoa 대신 스마트 컨트랙트 지갑을 사용할수 있도록 하는 제안이다.

사용 사례의 예

  • 권한 제어 
  • 트랜잭션 일괄 처리
  • 계정 복구
  • 거래 중지

용어 정리

  • UserOperation 
    • 사용자 대신 보낼 트랜잭션을 설명하는 구조, 혼동을 피하기 위해 트랜잭션이라는 이름은 지정 되지 않음
    • 트랜잭션과 마찬가지로 "sender", "to", "calldata", "maxFeePerGas", "maxPriorityFee", "signature", "nonce"가 포함된다
    • 트랜잭션과 달리 아래에 설명된 몇 가지 다른 필드가 포함되어 있다
    • 또한 "nonce" 및 "signature" 필드 사용은 프로토콜에 의해 정의되지 않고 각 계정 구현에 의해 정의된다
  • Sender
    • 사용자 작업을 보내는 계정 계약
  • EntryPoint
    • 사용자 작업 번들을 실행하기 위한 싱글톤 계약, 번들러/클라이언트는 지원되는 진입점을 허용 목록에 추가한다.
  • Bundler
    • 여러 사용자 작업을 벋늘하고 EntryPoint.handleOps() 트랜잭션을 생성하는 노드. 네트워크의 모든 블록 빌더가 번들러일 필요는 없다
  • Aggregator
    • 집계된 서명의 유효성을 검사하기 위해 계정에서 신뢰하는 컨트랙트. 번들러/클라이언트는 지원되는 집계자를 허용 목록에 추가한다.

요약

상위 수준 시스템에서 트랜잭션 멤풀의 기능을 복제하여 이용

사용자들은 트랜잭션 대신 userOperation Object를 노드에 보내고 이것들을 종합해 단일 트랜잭션으로 처리해 이더리움체인으로 보냄

이러한 번들 트랜잭션은 엔트리포인트 스마트 컨트랙트를 노출하며 이 컨트랙트는 안에있는 트랜잭션들을 수행하게 된다.

멀티시그, 번들 트랜잭션, 소셜리커버리, erc토큰 가스 수수료, 가능 

 

흐름

UserOperation 객체를 전용 사용자 작업 mempool로 보내면, 번들러라고 하는 전문화된 배우가 사용자 작업 mempool에서 수신하여 번들 트랜잭션을 만든다

번들 트랜잭션은 여러 개의 UserOperation 객체를 단일 handleOps 호출로 전역 진입점 계약에 패키지화한다

재생 공격을 방지하기 위해 서명은 chainid와 EntryPoint 주소에 의존해야 한다.

 

상세 설명 

흐름도를 따라 차근 차근 다시 살펴보자

1. EntryPoint Contract

우선 EIP-4337을 구현하는 전역 진입점 즉 EntryPoint 스마트 컨트랙트를 작성한다.

이 컨트랙트는 작업 처리르 위한 주요 함수인 handleOps()와 handleAggregatedOps()를 포함해야한다.

이 함수들은 사용자가 보낸 UserOperation 객체를 처리하고 해당 작업을 실행하는 역할을 담당한다.

 

2. IAccount Contract

각 사용자를 위해 별도의 계정 컨트랙트를 생성한다. 이 계정 컨트랙트는 사용자의 작업처리와 서명검증을 담당하며 

EIP-4337에서 정의한 IAccount 인터페이스를 구현해야한다. 계정 컨트랙트는 사용자가 보낸 userOperation을 검증하고

작업 처리를 위한 필요한 데이터를 반환하는 validateUserOp() 함수를 포함해야한다

 

3. UserOperation 객체 생성

사용자는 자신이 수행하려는 작업에 대한 정보를 담은 UserOperation 객체를 생성한다. 

이 객체에는 사용자의 주소, nonce, 실행할 컨트랙트코드(initCode), 호출데이터(callData), 필요한 가스 정보등이 포함되어야한다.

또한 작업의 유효성을 검증하기 위해 서명이 첨부되어야한다.

 

4. 번들 트랜잭션 처리

사용자는 생성한 UserOperation 객체를 번들러에 전송한다. 번들러는 전송받은 여러개의 UserOperation 객체를 하나의 번들 트랜잭션으로 처리한다. 이 트랜잭션은 EntryPoint 스마트 컨트랙트를 호출하며, 컨트랙트 내부에서 각 UserOperation 객체를 처리한다.

 

5. 서명 검증 및 작업 실행

번들 트랜잭션이 이더리움 체인에서 처리되면서 각 UserOperation의 서명을 검증하고 작업을 실행한다. 이 과정에서 계정 컨트랙트의 validateUserOp() 함수가 호출되어 서명이 올바른지 검증한다. 서명이 유효하면 작업이 수행되고 그렇지 않으면 오류가 발생한다.

 

계약의 핵심 인터페이스는 다음과 같다

EntryPoint.sol

pragma solidity ^0.8.0;

import "./IAccount.sol";
import "./IAggregator.sol";

contract EntryPoint {
    struct UserOperation {
        // UserOperation 구조체 정의
        address sender;
        uint256 nonce;
        bytes initCode;
        bytes callData;
        uint256 callGas;
        uint256 verificationGas;
        uint256 preVerificationGas;
        uint256 maxFeePerGas;
        uint256 maxPriorityFeePerGas;
        address paymaster;
        bytes paymasterData;
        bytes signature;
    }

    struct UserOpsPerAggregator {
        UserOperation[] userOps;
        IAggregator aggregator;
        bytes signature;
    }

//sample 1

    function handleOps(UserOperation[] calldata ops, address payable beneficiary) external {
        for (uint256 i = 0; i < ops.length; i++) {
            UserOperation memory op = ops[i];
            IAccount(op.sender).validateUserOp(op, /* userOpHash */, /* missingAccountFunds */);
            (bool success,) = op.target.call{value: op.value}(op.data);
            require(success, "Transaction execution failed");
        }
    }
    
//sample 2


    function handleOps(UserOperation[] calldata ops, address payable beneficiary) external {
        for (uint256 i = 0; i < ops.length; i++) {
            address target = ops[i].target;
            bytes memory callData = ops[i].callData;
            uint256 callGas = ops[i].callGas;
            // 각 작업을 수행하는 계정 컨트랙트 인스턴스를 가져옵니다.
            IAccount account = IAccount(target);
            // 계정 컨트랙트의 함수를 호출하여 작업을 처리합니다.
            (bool success,) = address(account).call{gas: callGas}(callData);
            require(success, "Function call failed");
        }
    }
    // 이 함수는 배열 형태로 전달된 UserOperation 객체들을 처리하고 수수료를 지불할 대상인 beneficiary를 전달한다.

    function handleAggregatedOps(
        UserOpsPerAggregator[] calldata opsPerAggregator,
        address payable beneficiary
    ) external {
        // handleAggregatedOps 함수 구현
    }
    //집계자(aggregator)에 의해 처리된 UserOperation 객체들을 처리한다. 집계된 작업들은 opsPerAggregator 배열로 
    //전달되며 수수료를 지불할 대상 주소인 beneficiary를 전달한다.

    function _processUserOperations(UserOperation[] calldata ops) private {
        // UserOperation 객체 처리하는 내부 함수 구현
    }
}

 

 

계정 핵심 인터페이스 : IAccount.sol

// IAccount.sol
pragma solidity ^0.8.0;

interface IAccount {
    function validateUserOp(
        EntryPoint.UserOperation calldata userOp,
        bytes32 userOpHash,
        uint256 missingAccountFunds
    ) external returns (uint256 validationData);
    
    // 이 함수는 UserOperation 객체를 검증한다. userOp는 검증할 사용자 작업,
    // userOpHash는 사용자 작업에 대한 해시값, 그리고 missingAccountFunds는 계정에서
    // 필요한 금액이 부족한 경우 전달 되는 값. 이 함수는 사용자 작업이 유효한지 확인하고
    // 필요한 경우 계정에 자금을 지불한다.
}

userOpHash는 userOp(서명 제외), entryPoint 및 chainId에 대한 해시이다.

  • 신뢰할 수 있는 EntryPoint를 호출자로 확인
  • 서명 집계를 지원하지 않는 계정의 경우, userOpHash의 유효한 서명인지 확인하고, 서명 불일치시 SIG_VALIDATION_FAILED를 반환해야 합니다. 다른 오류는 되돌립니다.
  • EntryPoint(호출자)에게 적어도 "missingAccountFunds"(계정의 예금이 충분하다면 0일 수 있음)를 지불해야 합니다.
  • 계정은 향후 트랜잭션을 처리하기 위해 최소 금액보다 더 지불할 수 있으며, 언제든지 withdrawTo를 발행하여 회수할 수 있습니다.
  • 반환 값은 authorizer, validUntil 및 validAfter 타임스탬프로 패키지화되어야 합니다.

집계자가 가져야 하는 핵심 인터페이스

IAggregator.sol

// IAggregator.sol
pragma solidity ^0.8.0;

interface IAggregator {
    function validateUserOpSignature(EntryPoint.UserOperation calldata userOp)
        external view returns (bytes memory sigForUserOp);
        
       // 이 함수는 사용자 작업의 서명을 검증한다. 전달된 userOp에 대한 서명이 유효한지 확인한다.

    function aggregateSignatures(EntryPoint.UserOperation[] calldata userOps)
        external view returns (bytes memory aggregatesSignature);
        
       // 이 함수는 여러개의 사용자 작업에 대한 서명을 집계한다. 전달된 userOps 배열에 있는 사용자 작업들의
       // 서명을 하나로 합친다.

    function validateSignatures(
        EntryPoint.UserOperation[] calldata userOps,
        bytes calldata signature
    ) view external;
    
    // 이 함수는 집계된 서명이 유효한지 확인한다. 전달된 userOps 배열에 있는 사용자 작업들과 해당 작업들의 
    // 집계된 서명인 signature를 검증한다.
    
    }

대충의 로직은 이렇고 제안자들이 구현한 erc4337을 살펴보자

 

 

지갑 생성 참고

양자 내성이 있는 알고리즘인 Lamport/Winternitz와 같은 서명 로직 사용 가능

 

전반적인 지갑 부분 흐름

  • 스마트 컨트랙트 개발 : 먼저 사용자 지갑에 대한 스마트컨트랙트를 개발해야한다. 
  • 컴파일 및 배포
  • 백엔드 구축 : 백엔드에서는 컨트랙트 지갑 생성을 처리하는 API를 구현해야한다. 사용자가 지갑을 생성하려고 요청한다면 백엔드는 블록체인에 트랜잭션을 전송하여 새로운 컨트랙트 인스턴스를 생성해야한다.
  • 트랜잭션 처리:  백엔드는 생성된 컨트랙트 주소를 사용자에게 전달하고 향후 사용자의 트랜잭션을 처리하는데 사용해야한다.
  • 프론트엔드 통합 : 프론트를 개발하여 사용자가 생성한 컨트랙트 지갑과 상호작용 할 수 있게 해야한다.

헷갈릴수도 있는 내용 정리

스마트 컨트랙트 지갑의 경우 개인키는 스마트 컨트랙트 주소와 직접 연결되어있지않다. 대신 사용자는 자신의 eoa의 개인키를 사용해 스마트 컨트랙트 지갑과 상호작용한다.

 

예로들어

  1. 사용자는 eoa privateKey를 사용해 스마트 컨트랙트 지갑 출금함수 호출 트랜잭션 서명
  2. 서명된 트랜잭션 이더리움 네트워크 전송
  3. 이더리움 네트워크는 트랜잭션 처리하고 스마트 컨트랙트 지감 출금 함수 실행
  4. 스마트 컨트랙트 지갑은 사용자의 eoa 계정으로 이더를 전송한다.

따라서 스마트컨트랙트을 사용하는 경우에도 사용자는 여전히 자신의 개인키를 관리해야한다. 하지만 사용자 정의 로직이나 다양한 인증 방식을 통해 보안성과 편의성을 높일수있다. 예로 다중서명이 있겠다.

 

내용이 길어지니 다음 장에서 컨트랙트를 분석하면서 직접 테스트까지해보겠다

'솔리디티' 카테고리의 다른 글

업그레이더블 컨트랙트 정리  (0) 2023.07.19
ERC-4337 , Account Abstraction  (0) 2023.04.04
call vs delegate call 알아보기  (0) 2023.01.04
솔리디티 Erc-20 토큰 기본 전문  (0) 2022.02.04
솔리디티 OwnerHelper, TokenLock  (0) 2022.02.04

댓글