Недавно я написал статью о Karma Money — альтернативной валютной системе, основанной на уникальном токене ERC-20. Я представлял Karma Money как закрытую систему, в которой пользователи также могут оплачивать комиссии за транзакции с помощью кармы. Создание собственного блокчейна могло бы стать одним из способов сделать это реальностью, но это непростая задача. Чтобы обеспечить безопасность и надежность блокчейна, нам необходимо создать необходимую инфраструктуру и достаточно большое сообщество. Было бы гораздо проще использовать существующий блокчейн. Существуют такие сети, как или , которые полностью совместимы с Ethereum и имеют очень низкие комиссии за транзакции. Комиссия за транзакцию ERC20 в этих цепочках обычно составляет менее 1 цента. Проблема в том, что эта комиссия должна быть оплачена в собственной криптовалюте сети, что может усложнить использование сети для пользователей. К счастью, существует связующее решение — транзакции с металлами .
Например, в транзакции кармы пользователь указывает сумму транзакции (например, 10 долларов кармы), адрес Ethereum, на который он хочет отправить сумму, и комиссию за транзакцию (в долларах кармы), которую он готов предложить. для сделки. Эта структура подписывается цифровой подписью и отправляется на ретрансляционный узел. Если узел находит комиссию за транзакцию приемлемой, он отправляет структуру с цифровой подписью в контракт кармы, который проверяет подпись и выполняет транзакцию. Поскольку комиссия за транзакцию выплачивается ретрансляционным узлом в собственной валюте блокчейна, пользователю кажется, что он платит долларами кармы за транзакцию, не нуждаясь в собственном блокчейне.
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 , предоставляемая позволяет легко подписывать структуры 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 — это общий стандарт для подписывания структур, что делает его лишь одним из многих вариантов реализации метатранзакций. Поскольку подпись можно проверять не только с помощью смарт-контрактов, она также может быть очень полезна в приложениях, не связанных с блокчейном. Например, его можно использовать для аутентификации на стороне сервера, когда пользователь идентифицирует себя с помощью своего закрытого ключа. Такая система может обеспечить высокий уровень безопасности, обычно связанный с криптовалютами, позволяя использовать веб-приложение только с аппаратным ключом. Кроме того, отдельные вызовы API также можно подписывать с помощью MetaMask.