Blockchain

Deep Dive into ERC-1271

스마트컨트랙트 지갑 서명 유효성 검사

작성자: Choi.eth

10 min read

image.png

Intro

외부 소유 계정(EOA)은 보유 중인 개인 키로 메시지에 서명할 수 있지만, 컨트랙트 계정(CA)은 개인키를 알 수 없기에 불가능하다. ERC-1271는 특정 스마트 컨트랙트 지갑을 대신한 서명이 유효한지 확인할 수 있는 표준 방법을 제안한다.

본 게시글은 서명을 할 수 없는 스마트 컨트랙트 지갑을 위해 대신한 서명과 해당 서명의 유효성을 어떻게 확인하는지에 대해 다루고 있다. 지금부터 ERC-1271에 대해 알아보자.

개인 지갑 vs 스마트컨트랙트 지갑

ERC-1271에 대해 알아보기 전에 개인 지갑과 스마트컨트랙트 지갑의 차이점에 대해 알아보자. 지갑이란 0x + 40개의 16진수로 이루어진 주소(address) 값을 의미한다. 지금까지 우리에게 지갑은 WEB3 자산(이더, 토큰, NFT 등)을 보관하고 관리하는 용도로 익숙하다. 이러한 규칙을 가진 어떠한 주소 값이라면 코인과 토큰을 보관할 수 있는 지갑이 될 수 있다.

  • 외부 소유 계정(EOA : Externall Owned Accounts) : 개인키에서 파생된 공개키 주소
  • 컨트랙트 계정(CA : Contract Accounts) : 스마트컨트랙트에 부여된 주소

우리가 블록체인 네트워크로 트랜잭션 보내기 위해 사용하는 지갑은 개인 지갑 또는 외부 소유 계정(EOA)라 하고, 컨트랙트 배포 시 부여되는 주소로 사용하는 지갑은 컨트랙트 계정(CA) 또는 스마트컨트랙트 지갑라고 한다.

개인 지갑은 개인키를 활용한 서명을 통해 트랜잭션을 만들어서 자산을 전송하거나 스마트컨트랙트의 기능을 실행시킬 수 있다. 반면에 스마트컨트랙트 지갑은 개인키가 없기에 서명을 통해 트랜잭션을 만들 수 없다. 때문에 스마트컨트랙트 지갑은 자산을 전송하기 위해서 컨트랙트 내부에 해당 기능을 구현하고 개인 지갑으로 실행하는 방식을 통해 비로소 자산을 관리할 수 있다.

디지털 서명의 3가지 용도

이더리움에서 사용하는 디지털 서명은 3가지 용도를 위해 사용한다.

  1. 서명은 EOA와 개인키 소유자가 트랜잭션 이행을 승인했음을 증명한다.
  2. 승인했음에 대한 부인방지(non-reudiation)를 보장한다.
  3. 트랜잭션이 서명된 후에는 그 누구도 트랜잭션 데이터를 수정할 수 없음을 증명한다.

우리는 위 3가지 용도를 통해서 지갑에 대한 소유권을 주장할 수 있다. 특히 이더리움의 디지털 서명은 ECDSA(Eliptic Curve Digital Signature Algorithm)의 타원 곡선 알고리즘을 사용하기에, 개인키를 노출하지 않고 해당 지갑 주소에 대한 소유권을 주장할 수 있다. 하지만 스마트컨트랙트 지갑은 개인키가 없어서 소유권을 주장할 수 없다. 이러한 한계에 대한 대응책으로 ERC-1271은 스마트컨트랙트 지갑을 위해, “직접 만드는 서명”이 아닌 특별히 맞춤화된 새로운 “서명 검증 방식”을 제안하며 등장했다. ERC-1271은 지갑의 소유권을 가진 사람의 서명을 검증하는 방식을 통해 Off-Chain 영역에서 스마트컨트랙트 지갑에 대한 소유권을 주장할 수 있다.

ERC-1271 Specification

ERC-1271은 지갑이(account) 스마트컨트랙트 지갑인 경우, 지갑의 소유권을 가진 사람이 본인의 개인키로 만든 서명을 통해 스마트컨트랙트 지갑의 소유주임을 확인하는 방법이 표준으로 정의되어 있다. 코드 레벨에서 알아보자.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";

contract ERC1271 is IERC1271 {
    address private _owner;

    constructor(address owner) {
        _owner = owner;
    }

    function owner() public view returns(address) {
        return _owner;
    }

    function isValidSignature(bytes32 hash, bytes memory signature)
            external
            view
            returns (bytes4 magicValue)
        {
            bool isValid = SignatureChecker.isValidSignatureNow(owner(), hash, signature);

            if (isValid) {
                return IERC1271.isValidSignature.selector;
            }

            return "";
        }
}

기능은 아주 간단하다. 지갑의 소유권을 가진 주인(Owner)가 서명한 메시지(hash)와 서명 값(signature)을 isValidSignature()의 파라미터에 담아서 보내면 끝이다.

서명값과 메시지를 검증하여 스마트컨트렉트 지갑의 owner임이 검증되면, 서명한 사람이 해당 지갑을 소유하고 있음을 증명할 수 있다.

그리고 증명이 성공하게 되면 0x1626ba7e (=bytes4(keccak256("isValidSignature(bytes32,bytes)"))을 리턴한다.

만약, 소유주가 아닌 사람이 만든 서명값을 통해 isValidSignature()하게 되면 서명 검증은 실패하며 빈 문자열을 리턴한다. 이와 같은 EIP-1271을 통해, 스마트컨트렉트 지갑의 소유권을 Off-Chain상에서 주장할 수 있다.

메시지 서명 만들기

EIP-1271 isValidSignature()의 파라미터에 들어가는 메시지(hash)와 서명 값(signature)을 ethers를 활용하여 만들어보자.

STEP 1 : 메시지 데이터 타입 변환

일반적으로 서명하는 메시지의 데이터타입은 문자열(String)이지만, isValidSignature() 파라미터의 메시지 데이터 타입은 bytes32로 되어 있다. 때문에 서명하고자 하는 메시지를 bytes32로 변환해야한다. 이를 위해 가장 쉬운방법은 keccak256() 해쉬함수를 통해 변환시키는 것이다.

import {ethers} from 'ethers';

const MSG = "This is MSG for EIP-1271"

let _hash = ethers.utils.toUtf8Bytes(MSG);
let hash = ethers.utils.keccak256(_hash)

작성하고자 하는 메시지는 어떠한 내용이 들어가도 상관없다. 어차피 서명은 내가 가진 개인키로 진행되기 때문에, 내용을 떠나서 디지털 서명을 통해 소유권을 가진사람이 서명한게 맞는지를 증명하기만 하면 된다.

keccak256() 해쉬함수는 bytes 형태의 데이터만 변환시키기에, 문자열을 먼저 bytes로 변환한 후, 암호화를 시켜 bytes32로 변환시켰다.

STEP 2 : 서명

const signer = new ethers.Wallet(process.env.ADMIN_KEY);

let signature = signer._signingKey().signDigest(hash)
console.log("sig.v:" + signature.v);
console.log("sig.r: " + signature.r);
console.log("sig.s: " + signature.s);

let data = {
        r:signature.r,
        s:signature.s,
        v:signature.v
    }

let _signature = ethers.utils.joinSignature(data);
console.log(`_signature : ${_signature}`);

isValidSignature()의 서명값(signature)은 서명 데이터 v,r,s를 합친 bytes의 형태다. keccak256로 해싱된 메시지를 서명하여 만든 서명값 v,r,s를 ethers.utils.joinSignature() 기능을 통해 합치면 된다.

이와 같은 방법으로 ethers를 통해 isValidSignature()를 통해 검증할 수 있는 메시지(hash)와 서명 값(signature)을 만들수 있다.

STEP 3 : REMIX 실습

%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2023-08-14_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_11.59.08.png

EIP-1271를 Remix에 배포하여 실습을 진행해보자. 현재 스마트컨트랙트 지갑의 Owner은 위와 같이 0xf39…2266 이다. 하지만, 실제로 0xf39…2266 지갑의 소유권을 가진사람은 누구인지 알 수 없다. 또한, 소유권을 증명하기 위해 당사자의 개인키를 요구할수도 없다. 때문에, 우리는 스마트컨트랙트 지갑에 EIP-1271 표준이 정의되어 있다면 주인에게 아무 메시지나 서명해서 개인키 대신, 서명값을 요구할 수 있다. 그리고 isValidSignature()를 통해 서명값을 검증하여 당사자가 소유권을 가지고 있는지 확인할 수 있다.

위 사진 처럼, 소유권이 검증되면 magicValue 라는 값이 리턴된다. 이를 통해 우리는 개인키 없이 스마트컨트랙트 지갑의 소유권을 증명 할 수 있다.

마무리

지금까지 ERC-1271에 대해 알아봤다. 지갑의 소유권을 증명하는 과정은 굉장히 중요하다. 이를 통해, 지갑에 보유중인 디지털 자산이 정말 본인것이 맞는지, 지갑과 자산을 우리 Dapp 서비스에서 이용할 수 있는 사용자인지 확인 할 수 있다. 이때 스마트컨트렉트 지갑의 소유권을 확인하는 방법은 ERC-1271가 등장하기 전까지 없었다.

현재 WEB3는 불편한 지갑 메커니즘을 다양한 방법으로 개선하기 위해 노력중이다. 불편한 지갑을 개선시키는 Dapp 서비스는 WEB2 유저의 유입을 이끌 수 있기 때문이다. 스마트컨트렉트 지갑은 현재 연구중인 지갑 컨텐츠이며, 대부분의 관련 표준에 ERC-1271가 포함되고 있다.

때문에 이 글을 통해 ERC-1271가 어떻게 구현되었는지, 어떻게 활용되는지 이해하고 다른 지갑 표준을 접하면 더욱 빠르게 이해할 수 있을 것이다.

Keep reading

모두 보기