這是PERMIT_TYPEHASH:
PERMIT_TYPEHASH 是函式名稱(大寫開頭)和所有引數(包括型別和名稱)的雜湊。目的是清楚地標誌簽名的函式。
簽名將在permit函式中處理,如果使用的PERMIT_TYPEHASH不是該特定函式的簽名,它將回退交易。這樣可以確保僅將簽名用於預期的功能。
然後是nonces對映:
mapping (address => uint) public nonces;
該對映記錄了特定持有人已使用了多少次簽名。建立簽名時,需要包含一個nonces值。執行permit時,所包含的nonce 值必須與該持有人到目前為止使用的簽名數完全匹配。這樣可以確保每個簽名僅使用一次。
所有這三個條件,即PERMIT_TYPEHASH,DOMAIN_SEPARATOR和nonce,確保每個簽名僅用於預期的合約,預期的函式,並且僅使用一次。
現在,讓我們看看如何在智慧合約中處理簽名。
permit 函式
permit是dai.sol[16]裡實現的函式,允許使用簽名來修改持有人的 allowance對spender授權的數量。
// --- 透過簽名授權 ---
function permit(
address holder, address spender,
uint256 nonce, uint256 expiry, bool allowed,
uint8 v, bytes32 r, bytes32 s
) external;
如你所見,permit有很多引數。它們是計算簽名所需的所有引數,加上簽名本身就是v,r和s。
你需要用引數建立簽名似乎很愚蠢,但是你確實需要。因為僅能從簽名中恢復簽名的地址。我們將使用所有引數和恢復的地址來確保簽名有效。
首先,我們使用確保安全性所需的所有引數來計算digest。作為簽名建立的一部分,holder將需要在鏈下計算出完全相同的digest:
bytes32 digest =
keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(
PERMIT_TYPEHASH,
holder,
spender,
nonce,
expiry,
allowed
))
));
使用ecrecover和v,r,s簽名,我們可以恢復地址。如果它是holder的地址,我們知道所有引數都匹配DOMAIN_SEPARATOR,PERMIT_TYPEHASH,nonce,holder,spender,expiry和allowed。哪怕是任何一點內容沒匹配,則簽名被拒絕:
require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
請注意這裡。簽名中有許多引數,其中一些引數有點模糊,例如chainId (它是 DOMAIN_SEPARATOR 的一部分)。它們中的任何一個不匹配都會導致簽名被拒絕,並帶有完全相同的錯誤提示,這讓鏈下除錯簽名很困難。
現在我們知道 holder 批准了這個函式呼叫。接下來,我們將證明簽名沒有被濫用。我們檢查當前時間是否在 expiry(過期)之前,這保證了僅在特定時間內許可有效。
require(expiry == 0 || now <= expiry, "Dai/permit-expired");
我們還會檢查簽名中的 nonce ,以便每個簽名只能使用一次。
require(nonce == nonces[holder]++, "Dai/invalid-nonce");
這些檢查都透過了!dai.sol[17]使spender可以使用的holder的代幣數量設定為最大值(即allowance設定為最大),並觸發一個事件,僅此而已。
uint wad = allowed ? uint(-1) : 0;
allowance[holder][spender] = wad;
emit Approval(holder, spender, wad);
dai.sol[18]合約對 allowance 使用的是二分法設定(譯者注:要麼是最大,要麼是 0), 在程式碼庫[19],有更傳統的方法。
建立鏈下簽名
建立簽名也許需要透過一些實踐才可以掌握它。我們將分三步複製智慧合約中permit的功能:
生成 DOMAIN_SEPARATOR
生成 digest
建立交易簽名
以下函式將生成 DOMAIN_SEPARATOR。它與dai.sol[20]建構函式中的程式碼相同,但在JavaScript中實現,並使用ethers.js[21]的keccak256,defaultAbiCoder和toUtfBytes,它需要代幣名稱和部署地址,以及chainId。假定代幣版本為“1”。
以下函式將為特定的permit呼叫生成digest。注意,holder,spender,nonce 和 expiry作為引數傳遞。為了清楚起見,它還傳遞了一個 approve.allowed 引數,儘管你可以將其始終設定為 true,否則簽名將被拒絕。從剛剛dai.sol[22]複製PERMIT_TYPEHASH。
一旦我們有了digest,對其進行簽名就相對容易了。我們從[digest]中刪除0x字首後,使用ethereumjs-util[23]中的ecsign。請注意,我們需要使用者私鑰才能執行此操作。
在程式碼中,我們將按以下方式呼叫這些函式:
請注意,對permit的呼叫需要重用用於建立digest的所有引數。只有在這種情況下,簽名才有效。
還要注意的是,此程式碼段中僅有的兩個交易是由user2呼叫的。user1是holder,是建立digest並簽名的使用者。但是,user1並沒有花費任何gas。
user1將簽名提供給user2,後者使用它來執行user1授權的 permit 和transferFrom。
從 user1的角度來看,這是一次“無 gas”交易, 他沒有花一分錢。
結論
本文介紹瞭如何使用“無Gas”交易,闡明瞭“無Gas”實際上意味著將Gas成本轉移給其他人。為此,我們需要一個智慧合約中的功能,該功能可以處理預先簽署的交易,並且需要進行大量的資料檢驗以確保一切安全。
但是,使用此模式有很多好處,因此,它被廣泛使用。簽名允許將交易 gas 成本從使用者轉移到服務提供商,從而在許多情況下消除了相當大的障礙。它還允許實現更高階的委派模式,通常會對UX進行相當大的改進。
已為您提供入門程式碼庫[24],請使用它。
參考資料
[1]登鏈翻譯計劃: https://github.com/lbc-team/Pioneer
[2]Tiny熊: https://learnblockchain.cn/people/15
[3]此儲存庫: https://github.com/albertocuestacanada/ERC20Permit?ref=learnblockchain.cn
[4]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[5]鑽進了兔子洞: https://learnblockchain.cn/docs/eips/eip-712.html
[6]EIP712: https://learnblockchain.cn/docs/eips/eip-712.html
[7]EIP712: https://learnblockchain.cn/docs/eips/eip-712.html
[8]EIP2612: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2612.md?ref=learnblockchain.cn
[9]EIP712: https://learnblockchain.cn/docs/eips/eip-712.html
[10]EIP2612: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2612.md?ref=learnblockchain.cn
[11]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[12]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[13]EIP2612: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2612.md?ref=learnblockchain.cn
[14]EIP712: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md?ref=learnblockchain.cn
[15]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol
[16]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[17]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[18]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[19]程式碼庫: https://github.com/albertocuestacanada/ERC20Permit?ref=learnblockchain.cn
[20]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[21]ethers.js: https://learnblockchain.cn/docs/ethers.js/
[22]dai.sol: https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=learnblockchain.cn
[23]digest]中刪除0x字首後,使用[ethereumjs-util: https://github.com/ethereumjs/ethereumjs-util?ref=learnblockchain.cn
[24]程式碼庫: https://github.com/albertocuestacanada/ERC20Permit?ref=learnblockchain.cn
[25]Cell Network: https://www.cellnetwork.io/?utm_souce=learnblockchain
[26]@albertocuestacanada: https://hackernoon.com/u/albertocuestacanada