paint-brush
Безгазовые метатранзакции Ethereum с использованием EIP-712к@thebojda
1,816 чтения
1,816 чтения

Безгазовые метатранзакции Ethereum с использованием EIP-712

к Laszlo Fazekas12m2023/10/17
Read on Terminal Reader

Слишком долго; Читать

В случае метатранзакции пользователь создает структуру, описывающую транзакцию, а затем подписывает ее своим закрытым ключом. Это похоже на выписку чека для кого-то. Транзакция с цифровой подписью затем отправляется на ретрансляционный узел, который передает ее в смарт-контракт. Контракт проверяет подпись и, если она действительна, выполняет транзакцию. Релейный узел платит за выполнение контракта.
featured image - Безгазовые метатранзакции Ethereum с использованием EIP-712
Laszlo Fazekas HackerNoon profile picture
0-item


Недавно я написал статью о Karma Money — альтернативной валютной системе, основанной на уникальном токене ERC-20. Я представлял Karma Money как закрытую систему, в которой пользователи также могут оплачивать комиссии за транзакции с помощью кармы. Создание собственного блокчейна могло бы стать одним из способов сделать это реальностью, но это непростая задача. Чтобы обеспечить безопасность и надежность блокчейна, нам необходимо создать необходимую инфраструктуру и достаточно большое сообщество. Было бы гораздо проще использовать существующий блокчейн. Существуют такие сети, как Gnosis или Polygon , которые полностью совместимы с Ethereum и имеют очень низкие комиссии за транзакции. Комиссия за транзакцию ERC20 в этих цепочках обычно составляет менее 1 цента. Проблема в том, что эта комиссия должна быть оплачена в собственной криптовалюте сети, что может усложнить использование сети для пользователей. К счастью, существует связующее решение — транзакции с металлами EIP-712 .


В случае метатранзакции пользователь создает структуру, описывающую транзакцию, а затем подписывает ее цифровым способом своим закрытым ключом. Это похоже на выписку чека для кого-то. Транзакция с цифровой подписью затем отправляется на ретрансляционный узел, который передает ее в смарт-контракт. Контракт проверяет подпись и, если она действительна, выполняет транзакцию. Релейный узел платит за выполнение контракта.


Например, в транзакции кармы пользователь указывает сумму транзакции (например, 10 долларов кармы), адрес Ethereum, на который он хочет отправить сумму, и комиссию за транзакцию (в долларах кармы), которую он готов предложить. для сделки. Эта структура подписывается цифровой подписью и отправляется на ретрансляционный узел. Если узел находит комиссию за транзакцию приемлемой, он отправляет структуру с цифровой подписью в контракт кармы, который проверяет подпись и выполняет транзакцию. Поскольку комиссия за транзакцию выплачивается ретрансляционным узлом в собственной валюте блокчейна, пользователю кажется, что он платит долларами кармы за транзакцию, не нуждаясь в собственном блокчейне.


После теории перейдем к практике.


Стандарт EIP-712 определяет, как подписывать пакеты структурированных данных стандартизированным способом. MetaMask отображает эти структурированные данные в читаемом для пользователя формате. Структура, совместимая с EIP-712, показанная на MetaMask ( можно протестировать по этому 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 — это описание общей структуры, содержащей метаданные. Поле имени — это имя структуры, поле версии — это версия определения структуры, а поля ChainId и VerificationContract определяют, для какого контракта предназначено сообщение. Исполняемый контракт проверяет эти метаданные, чтобы гарантировать, что подписанная транзакция выполняется только в целевом контракте.


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))


Переменная типов определяет структуру транзакции. «От» — это адрес отправителя, а «Кому» — адрес получателя. Сумма представляет собой количество токенов, подлежащих переводу. Комиссия — это «количество» токенов, которые мы предлагаем узлу ретрансляции в обмен на выполнение нашей транзакции и покрытие стоимости в собственной валюте цепочки. «Nonce» служит счетчиком, гарантирующим уникальность транзакции. Без этого поля транзакция могла бы выполняться несколько раз. Однако благодаря nonce подписанная транзакция может быть выполнена только один раз.


Функция SignTypedData , предоставляемая ethers.js, позволяет легко подписывать структуры EIP-712. Он делает то же самое, что и код, представленный ранее, но с более простым использованием.


Метаперенос — это метод кармического контракта для выполнения метатранзакции. Давайте посмотрим, как это работает:


 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 , который включает в себя образец смарт-контракта и пример кода JavaScript .


Если кратко, то суть в том, что мы объединяем TYPEHASH (который является хешем описания структуры) с полями структуры с помощью abi.encode. Затем создает хэш keccak256. Хэш передается методу _hashTypedDataV4, унаследованному от контракта EIP712 OpenZeppelin в контракте Karma. Эта функция добавляет метаданные в нашу структуру и генерирует окончательный хэш, делая проверку структуры очень простой и прозрачной. Самая внешняя функция — ECDSA.tryRecover, которая пытается восстановить адрес подписывающего лица по хешу и подписи. Если он соответствует адресу параметра «from», подпись действительна. В конце кода выполняется фактическая транзакция, и узел ретрансляции, выполняющий транзакцию, получает комиссию.


EIP-712 — это общий стандарт для подписывания структур, что делает его лишь одним из многих вариантов реализации метатранзакций. Поскольку подпись можно проверять не только с помощью смарт-контрактов, она также может быть очень полезна в приложениях, не связанных с блокчейном. Например, его можно использовать для аутентификации на стороне сервера, когда пользователь идентифицирует себя с помощью своего закрытого ключа. Такая система может обеспечить высокий уровень безопасности, обычно связанный с криптовалютами, позволяя использовать веб-приложение только с аппаратным ключом. Кроме того, отдельные вызовы API также можно подписывать с помощью MetaMask.


Я надеюсь, что этот краткий обзор стандарта EIP-712 вдохновил многих и что вы сможете использовать его как в проектах, основанных на блокчейне, так и в проектах, не связанных с блокчейном.


Каждый код доступен в репозитории karma Money на GitHub .