paint-brush
Siêu giao dịch Ethereum không cần dùng gas bằng cách sử dụng EIP-712từ tác giả@thebojda
1,816 lượt đọc
1,816 lượt đọc

Siêu giao dịch Ethereum không cần dùng gas bằng cách sử dụng EIP-712

từ tác giả Laszlo Fazekas12m2023/10/17
Read on Terminal Reader

dài quá đọc không nổi

Trong trường hợp siêu giao dịch, người dùng tạo cấu trúc mô tả giao dịch và sau đó ký điện tử bằng khóa riêng của họ. Nó tương tự như việc viết séc cho ai đó. Giao dịch được ký điện tử sau đó được gửi đến một nút chuyển tiếp, nút này sẽ gửi nó tới một hợp đồng thông minh. Hợp đồng xác minh chữ ký và nếu nó hợp lệ thì sẽ thực hiện giao dịch. Nút chuyển tiếp trả tiền cho việc thực hiện hợp đồng.
featured image - Siêu giao dịch Ethereum không cần dùng gas bằng cách sử dụng EIP-712
Laszlo Fazekas HackerNoon profile picture
0-item


Gần đây tôi đã viết một bài về Karma Money , một hệ thống tiền tệ thay thế dựa trên mã thông báo ERC-20 duy nhất. Tôi đã hình dung Karma Money như một hệ thống khép kín, nơi người dùng cũng có thể trả phí giao dịch bằng nghiệp chướng. Xây dựng chuỗi khối của riêng chúng tôi sẽ là một cách để biến điều này thành hiện thực, nhưng đó là một nhiệm vụ đầy thách thức. Để đảm bảo tính bảo mật và độ tin cậy của blockchain, chúng tôi cần thiết lập cơ sở hạ tầng cần thiết và một cộng đồng đủ lớn. Sẽ đơn giản hơn nhiều nếu sử dụng một blockchain hiện có. Có những chuỗi như Gnosis hoặc Polygon , hoàn toàn tương thích với Ethereum và có phí giao dịch rất thấp. Phí cho giao dịch ERC20 trên các chuỗi này thường dưới 1 xu. Vấn đề là khoản phí này phải được thanh toán bằng tiền điện tử của chính chuỗi, điều này có thể gây phức tạp cho việc sử dụng chuỗi của người dùng. May mắn thay, có một giải pháp bắc cầu, đó là giao dịch kim loại EIP-712 .


Trong trường hợp siêu giao dịch, người dùng tạo cấu trúc mô tả giao dịch và sau đó ký điện tử bằng khóa riêng của họ. Nó tương tự như việc viết séc cho ai đó. Giao dịch được ký điện tử sau đó được gửi đến một nút chuyển tiếp, nút này sẽ gửi nó tới một hợp đồng thông minh. Hợp đồng xác minh chữ ký và nếu nó hợp lệ thì sẽ thực hiện giao dịch. Nút chuyển tiếp trả tiền cho việc thực hiện hợp đồng.


Ví dụ: trong một giao dịch karma, người dùng cung cấp số tiền của giao dịch (ví dụ: 10 đô la karma), địa chỉ Ethereum nơi họ muốn gửi số tiền đó và phí giao dịch (bằng đô la karma) mà họ sẵn sàng cung cấp cho giao dịch. Cấu trúc này được ký điện tử và gửi đến nút chuyển tiếp. Nếu nút thấy phí giao dịch có thể chấp nhận được thì nó sẽ gửi cấu trúc được ký điện tử cho hợp đồng nghiệp vụ để xác minh chữ ký và thực hiện giao dịch. Vì phí giao dịch được thanh toán bằng tiền tệ gốc của blockchain bằng nút chuyển tiếp, nên người dùng có vẻ như đang thanh toán bằng đô la nghiệp chướng cho giao dịch mà không cần blockchain của riêng họ.


Sau lý thuyết, chúng ta hãy xem thực hành.


Tiêu chuẩn EIP-712 xác định cách ký các gói dữ liệu có cấu trúc theo cách chuẩn hóa. MetaMask hiển thị những dữ liệu có cấu trúc này ở định dạng có thể đọc được cho người dùng. Cấu trúc tuân thủ EIP-712, như được hiển thị trên MetaMask ( có thể được kiểm tra trên URL này ) trông như thế này:


Cấu trúc tuân thủ EIP-712


Giao dịch trên được tạo bằng mã đơn giản sau:


 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_def định là mô tả về cấu trúc chung, chứa siêu dữ liệu. Trường tên là tên của cấu trúc, trường phiên bản là phiên bản định nghĩa của cấu trúc và các trường chainId và verifyContract xác định thông báo dành cho hợp đồng nào. Hợp đồng thực thi xác minh siêu dữ liệu này để đảm bảo rằng giao dịch đã ký chỉ được thực hiện trên hợp đồng đích.


karma_request_domain chứa giá trị cụ thể của siêu dữ liệu được xác định bởi cấu trúc EIP712Domain.


Cấu trúc thực tế mà chúng tôi gửi tới MetaMask để lấy chữ ký được chứa trong biến transfer_request . Khối loại chứa các định nghĩa loại. Ở đây, phần tử đầu tiên là định nghĩa EIP712Domain bắt buộc, mô tả siêu dữ liệu. Tiếp theo là định nghĩa cấu trúc thực tế, trong trường hợp này là TransferRequest. Đây là cấu trúc sẽ xuất hiện trong MetaMask cho người dùng. Khối miền chứa giá trị cụ thể của siêu dữ liệu, trong khi thông báo chứa cấu trúc cụ thể mà chúng tôi muốn ký với người dùng.


Khi nói đến tiền nghiệp báo, một ví dụ về cách kết hợp một siêu giao dịch và gửi đến hợp đồng thông minh sẽ như thế này:


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


Biến loại xác định cấu trúc của giao dịch. “Từ” là địa chỉ của người gửi, trong khi “đến” là địa chỉ của người nhận. Số lượng đại diện cho số lượng token được chuyển. Phí là “số lượng” token mà chúng tôi cung cấp cho nút chuyển tiếp để đổi lấy việc thực hiện giao dịch của chúng tôi và trang trải chi phí bằng loại tiền tệ gốc của chuỗi. “Nonce” đóng vai trò như một bộ đếm để đảm bảo tính duy nhất của giao dịch. Nếu không có trường này, một giao dịch có thể được thực hiện nhiều lần. Tuy nhiên, do nonce nên một giao dịch đã ký chỉ có thể được thực hiện một lần.


Hàm signTypedData do ethers.js cung cấp giúp bạn dễ dàng ký các cấu trúc EIP-712. Nó thực hiện tương tự như mã được trình bày trước đó nhưng với cách sử dụng đơn giản hơn.


MetaTransfer là phương thức của hợp đồng nghiệp lực để thực hiện giao dịch meta. Hãy xem nó hoạt động như thế nào:


 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; }


Để xác thực chữ ký, trước tiên chúng ta phải tạo hàm băm của cấu trúc. Các bước chính xác để thực hiện việc này được mô tả chi tiết trong tiêu chuẩn EIP-712 , bao gồm hợp đồng thông minh mẫumã javascript mẫu .


Tóm lại, điều cốt lõi là chúng tôi kết hợp TYPEHASH (là hàm băm của mô tả cấu trúc) với các trường của cấu trúc bằng abi.encode. Sau đó tạo ra hàm băm keccak256. Hàm băm được chuyển sang phương thức _hashTypedDataV4, được kế thừa từ hợp đồng EIP712 OpenZeppelin trong hợp đồng Karma. Hàm này thêm siêu dữ liệu vào cấu trúc của chúng ta và tạo ra hàm băm cuối cùng, giúp việc xác thực cấu trúc trở nên rất đơn giản và minh bạch. Hàm ngoài cùng là ECDSA.tryRecover, hàm này cố gắng khôi phục địa chỉ của người ký từ hàm băm và chữ ký. Nếu nó khớp với địa chỉ của tham số “from“ thì chữ ký là hợp lệ. Ở cuối mã, giao dịch thực tế được thực hiện và nút chuyển tiếp thực hiện giao dịch sẽ nhận được phí.


EIP-712 là một tiêu chuẩn chung cho các cấu trúc ký tên, khiến nó chỉ là một trong nhiều ứng dụng để thực hiện các giao dịch meta. Vì chữ ký có thể được xác thực không chỉ bằng hợp đồng thông minh, nó còn có thể rất hữu ích trong các ứng dụng không phải blockchain. Ví dụ: nó có thể được sử dụng để xác thực phía máy chủ, trong đó người dùng tự nhận dạng mình bằng khóa riêng của họ. Một hệ thống như vậy có thể cung cấp mức độ bảo mật cao thường được liên kết với tiền điện tử, cho phép khả năng sử dụng ứng dụng web chỉ bằng khóa phần cứng. Ngoài ra, các lệnh gọi API riêng lẻ cũng có thể được ký với sự trợ giúp của MetaMask.


Tôi hy vọng rằng tổng quan ngắn gọn này về tiêu chuẩn EIP-712 đã truyền cảm hứng cho nhiều người và bạn sẽ có thể sử dụng nó trong cả các dự án dựa trên blockchain và không dựa trên blockchain.


Mọi mã đều có sẵn trên GitHub repo của karma money .