paint-brush
EIP-712를 사용한 Ethereum Gasless 메타트랜잭션by@thebojda
1,697
1,697

EIP-712를 사용한 Ethereum Gasless 메타트랜잭션

Laszlo Fazekas12m2023/10/17
Read on Terminal Reader

메타트랜잭션의 경우 사용자는 트랜잭션을 설명하는 구조를 생성한 다음 개인 키를 사용하여 디지털 방식으로 서명합니다. 이는 누군가에게 수표를 써주는 것과 비슷합니다. 디지털 서명된 거래는 릴레이 노드로 전송되어 스마트 계약에 제출됩니다. 계약은 서명을 확인하고, 유효한 경우 거래를 실행합니다. 릴레이 노드는 계약 실행 비용을 지불합니다.
featured image - EIP-712를 사용한 Ethereum Gasless 메타트랜잭션
Laszlo Fazekas HackerNoon profile picture
0-item


저는 최근 고유한 ERC-20 토큰을 기반으로 한 대체 통화 시스템인 Karma Money 에 대한 기사를 썼습니다. 나는 Karma Money를 사용자가 Karma로 거래 수수료를 지불할 수도 있는 폐쇄형 시스템으로 상상했습니다. 자체 블록체인을 구축하는 것은 이를 현실로 만드는 한 가지 방법이겠지만 이는 어려운 작업입니다. 블록체인의 보안과 신뢰성을 보장하려면 필요한 인프라와 충분히 큰 커뮤니티를 구축해야 합니다. 기존 블록체인을 사용하는 것이 훨씬 간단할 것입니다. 이더리움과 완벽하게 호환되고 거래 수수료가 매우 낮은 Gnosis 또는 Polygon 과 같은 체인이 있습니다. 이러한 체인의 ERC20 거래 수수료는 일반적으로 1센트 미만입니다. 문제는 이 수수료를 체인 자체 암호화폐로 지불해야 하기 때문에 사용자의 체인 사용이 복잡해질 수 있다는 점입니다. 다행스럽게도 EIP-712 메탈 트랜잭션이라는 브리징 솔루션이 있습니다.


메타트랜잭션의 경우 사용자는 트랜잭션을 설명하는 구조를 만든 다음 개인 키를 사용하여 디지털 방식으로 서명합니다. 이는 누군가에게 수표를 써주는 것과 비슷합니다. 디지털 서명된 거래는 릴레이 노드로 전송되어 스마트 계약에 제출됩니다. 계약은 서명을 확인하고, 유효한 경우 거래를 실행합니다. 중계 노드는 계약 실행 비용을 지불합니다.


예를 들어, 카르마 거래에서 사용자는 거래 금액(예: 10 카르마 달러), 금액을 보내려는 이더리움 주소, 제공하려는 거래 수수료(카르마 달러 단위)를 제공합니다. 거래를 위해. 이 구조는 디지털 서명되어 릴레이 노드로 전송됩니다. 노드가 수용 가능한 거래 수수료를 찾으면 디지털 서명된 구조를 카르마 계약에 제출하여 서명을 확인하고 거래를 실행합니다. 거래 수수료는 중계 노드에 의해 블록체인의 기본 통화로 지불되기 때문에 사용자에게는 자체 블록체인이 필요하지 않고 거래에 대해 카르마 달러로 지불하는 것처럼 보입니다.


이론에 이어 실습을 살펴보겠습니다.


EIP-712 표준은 표준화된 방식으로 구조화된 데이터 패키지에 서명하는 방법을 정의합니다. MetaMask는 이러한 구조화된 데이터를 사용자가 읽을 수 있는 형식으로 표시합니다. MetaMask에 표시된 EIP-712 호환 구조( 이 URL에서 테스트 가능 )는 다음과 같습니다.


EIP-712 호환 구조


위의 트랜잭션은 다음과 같은 간단한 코드를 사용하여 생성되었습니다.


 async function main() { if (!window.ethereum || !window.ethereum.isMetaMask) { console.log("Please install MetaMask") return } const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); const chainId = await window.ethereum.request({ method: 'eth_chainId' }); const eip712domain_type_definition = { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ] } const karma_request_domain = { "name": "Karma Request", "version": "1", "chainId": chainId, "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" } document.getElementById('transfer_request')?.addEventListener("click", async function () { const transfer_request = { "types": { ...eip712domain_type_definition, "TransferRequest": [ { "name": "to", "type": "address" }, { "name": "amount", "type": "uint256" } ] }, "primaryType": "TransferRequest", "domain": karma_request_domain, "message": { "to": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", "amount": 1234 } } let signature = await window.ethereum.request({ "method": "eth_signTypedData_v4", "params": [ accounts[0], transfer_request ] }) alert("Signature: " + signature) }) } main()


eip712domain_type_definition 은 메타데이터가 포함된 일반 구조에 대한 설명입니다. name 필드는 구조의 이름이고, version 필드는 구조의 정의 버전이며, chainId 및 verifyContract 필드는 메시지가 의도된 계약을 결정합니다. 실행 중인 계약은 서명된 트랜잭션이 대상 계약에서만 실행되도록 하기 위해 이 메타데이터를 확인합니다.


karma_request_domain에는 EIP712Domain 구조에 의해 정의된 메타데이터의 특정 값이 포함되어 있습니다.


서명을 위해 MetaMask에 보내는 실제 구조는 transfer_request 변수에 포함되어 있습니다. 유형 블록에는 유형 정의가 포함되어 있습니다. 여기서 첫 번째 요소는 메타데이터를 설명하는 필수 EIP712Domain 정의입니다. 그 다음에는 실제 구조 정의가 따르는데, 이 경우 TransferRequest입니다. 이는 사용자를 위해 MetaMask에 나타날 구조입니다. 도메인 블록에는 메타데이터의 특정 값이 포함되어 있고, 메시지에는 사용자와 서명하려는 특정 구조가 포함되어 있습니다.


카르마 머니와 관련하여 메타트랜잭션을 모아 스마트 계약으로 전송하는 방법의 예는 다음과 같습니다.


 const types = { "TransferRequest": [ { "name": "from", "type": "address" }, { "name": "to", "type": "address" }, { "name": "amount", "type": "uint256" }, { "name": "fee", "type": "uint256" }, { "name": "nonce", "type": "uint256" } ] } let nonce = await contract.connect(MINER).getNonce(ALICE.address) const message = { "from": ALICE.address, "to": JOHN.address, "amount": 10, "fee": 1, "nonce": nonce } const signature = await ALICE.signTypedData(karma_request_domain, types, message) await contract.connect(MINER).metaTransfer(ALICE.address, JOHN.address, 10, 1, nonce, signature) assert.equal(await contract.balanceOf(ALICE.address), ethers.toBigInt(11))


유형 변수는 트랜잭션의 구조를 정의합니다. "from"은 보내는 사람의 주소이고, "to"는 받는 사람의 주소입니다. 금액은 전송될 토큰의 수량을 나타냅니다. 수수료는 거래를 실행하고 체인의 기본 통화로 비용을 충당하는 대가로 중계 노드에 제공하는 토큰의 "금액"입니다. "nonce"는 트랜잭션의 고유성을 보장하는 카운터 역할을 합니다. 이 필드가 없으면 트랜잭션이 여러 번 실행될 수 있습니다. 그러나 nonce 덕분에 서명된 트랜잭션은 한 번만 실행될 수 있습니다.


ethers.js 에서 제공하는 signTypedData 기능을 사용하면 EIP-712 구조에 쉽게 서명할 수 있습니다. 이전에 제시된 코드와 동일한 작업을 수행하지만 사용법이 더 간단합니다.


MetaTransfer는 메타 트랜잭션을 실행하기 위한 카르마 계약의 방법입니다. 어떻게 작동하는지 살펴보겠습니다.


 function metaTransfer( address from, address to, uint256 amount, uint256 fee, uint256 nonce, bytes calldata signature ) public virtual returns (bool) { uint256 currentNonce = _useNonce(from, nonce); (address recoveredAddress, ECDSA.RecoverError err) = ECDSA.tryRecover( _hashTypedDataV4( keccak256( abi.encode( TRANSFER_REQUEST_TYPEHASH, from, to, amount, fee, currentNonce ) ) ), signature ); require( err == ECDSA.RecoverError.NoError && recoveredAddress == from, "Signature error" ); _transfer(recoveredAddress, to, amount); _transfer(recoveredAddress, msg.sender, fee); return true; }


서명의 유효성을 검사하려면 먼저 구조의 해시를 생성해야 합니다. 이를 수행하는 정확한 단계는 샘플 스마트 계약샘플 자바스크립트 코드를 포함하는 EIP-712 표준 에 자세히 설명되어 있습니다.


요약하자면, 본질은 abi.encode를 사용하여 TYPEHASH(구조 설명의 해시)를 구조의 필드와 결합한다는 것입니다. 그런 다음 keccak256 해시를 생성합니다. 해시는 Karma 계약의 EIP712 OpenZeppelin 계약에서 상속된 _hashTypedDataV4 메서드로 전달됩니다. 이 기능은 구조에 메타데이터를 추가하고 최종 해시를 생성하여 구조 검증을 매우 간단하고 투명하게 만듭니다. 가장 바깥쪽 함수는 해시 및 서명에서 서명자의 주소를 복구하려고 시도하는 ECDSA.tryRecover입니다. "from" 매개변수의 주소와 일치하면 서명이 유효한 것입니다. 코드의 끝에서는 실제 거래가 실행되고 거래를 수행하는 중계 노드가 수수료를 받습니다.


EIP-712는 구조 서명을 위한 일반 표준으로, 메타 트랜잭션을 구현하는 데 사용되는 많은 용도 중 하나일 뿐입니다. 서명은 스마트 계약으로 검증될 수 있을 뿐만 아니라 블록체인이 아닌 애플리케이션에서도 매우 유용할 수 있습니다. 예를 들어, 사용자가 개인 키로 자신을 식별하는 서버 측 인증에 사용될 수 있습니다. 이러한 시스템은 일반적으로 암호화폐와 관련된 높은 수준의 보안을 제공할 수 있으므로 하드웨어 키로만 웹 애플리케이션을 사용할 수 있습니다. 또한 MetaMask의 도움으로 개별 API 호출에 서명할 수도 있습니다.


EIP-712 표준에 대한 이 간략한 개요가 많은 사람들에게 영감을 주었으며 블록체인 기반 프로젝트와 비블록체인 프로젝트 모두에서 이를 활용할 수 있기를 바랍니다.


모든 코드는 Karma Money의 GitHub 저장소 에서 사용할 수 있습니다.