驗證 EIP-712 時恢復的帳戶不一致(javascript / python)
我有一個簽署 EIP-712 消息的客戶端:
eip_712.js``ethers: 5.7.1
(部:)const { Wallet } = require("@ethersproject/wallet"); const { _TypedDataEncoder } = require("@ethersproject/hash"); console.log("EIP-712 JS"); // ! this is a test private key, DO NOT USE IT const privKey = '0x0123456789012345678901234567890123456789012345678901234567890123'; const signer = new Wallet(privKey); console.log("Signer is: ", signer.address); // ! You need to fix your ethers version because _signTypedData is experimental // ! feature. // * https://docs.ethers.io/v5/api/signer/#Signer-signTypedData const domain = { chainId: 0, name: '', verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', version: '' }; // The named list of all type definitions const types = { Player: [ { name: 'wallet', type: 'address' }, { name: 'email', type: 'string' } ], Message: [ { name: 'player', type: 'Player' }, ] }; // The data to sign const value = { player: { wallet: "0x14791697260E4c9A71f18484C9f997B308e59325", email: "steve@jobs.com", } }; console.log( "Signing this digest: ", _TypedDataEncoder.hash(domain, types, value) ); signer._signTypedData( domain, types, value ).then(signature => { console.log("Signature is: ", signature); }).catch(e => { console.log("Something went wrong", e); });
現在我正在嘗試驗證 python 中的 from 帳戶:
eip_712.py
(部門:eip712 = "0.1.4"
,eth-account = "0.7.0"
)#!/usr/bin/env python from eip712.messages import EIP712Message, EIP712Type from eth_account import Account print("EIP-712 PY") class Player(EIP712Type): wallet: "address" email: "string" class Message(EIP712Message): _chainId_: "uint256" = 0 _name_: "string" = '' _verifyingContract_: "address" = '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' _version_: "string" = '' player: Player signature = bytes.fromhex('f65e247fc142c6fa98febae4dba127be1694e83a5fce938c4389945594a5acd454771cdf810b90f2f83d60c69f8d576d5ff8dab957f57b2a247c86baffae6d011c') message = Message(player=Player( wallet='0x14791697260E4c9A71f18484C9f997B308e59325', email='steve@jobs.com' )) # this is the digest that the user signs on the client side print('Digest that was signed: ', message.body.hex()) from_account = Account.recover_message(message.signable_message, signature=signature) print('Account that signed the message: ', from_account)
以上兩個的輸出是:
EIP-712 JS Signer is: 0x14791697260E4c9A71f18484C9f997B308e59325 Signing this digest: 0x044b3a930b5d539e1ec6f5ab7de69a1c6c0f103a11c6377adbd81e92763df9be Signature is: 0xf65e247fc142c6fa98febae4dba127be1694e83a5fce938c4389945594a5acd454771cdf810b90f2f83d60c69f8d576d5ff8dab957f57b2a247c86baffae6d011c EIP-712 PY Digest that was signed: e5477989520456a98eb64ab80304b4e9b1858fe542121f4722a0ea1e12aed8c9 Account that signed the message: 0xE01Fc9697648F725409D75B08f5e7D98CddD13bD
看起來這兩種方法簽名的摘要不同?
編輯:Python 端的摘要實際上並不是用於恢復帳戶的摘要。我進入
eth_account
lib 並列印了實際的摘要。這是文件message_hash
中的第 450 行account.py
。實際使用的摘要是:0xa7875cf3863821279f6b8d2c254e34caec0e7376cb37fda6387150b0ec3aaf96
. 這意味著eth_account
正在使用與ethers.js
生成的摘要不同的摘要。我會將摘要硬編碼ethers.js
到其中eth_account
以驗證摘要是否確實正確,它是否會準確地恢復地址。編輯:確實,如果我使用來自 的摘要
ethers.js
,那麼eth_account
正在恢復正確的簽名者地址。這意味著如果我們弄清楚為什麼eth_account
會產生不同的摘要,那麼我們就在做生意。編輯:我已經驗證,不使用
eip712
庫而直接對結構化數據進行編碼,會在 Python 端產生完全相同的錯誤摘要。即以下也是無效的:signable_message = eth_account.messages.encode_structured_data( { "message": { "player": { "wallet": "0x14791697260E4c9A71f18484C9f997B308e59325", "email": "steve@jobs.com" } }, "types": { "EIP712Domain": [ {"name": "chainId", "type": "uint256"}, {"name": "name", "type": "string"}, {"name": "verifyingContract", "type": "address"}, {"name": "version", "type": "string"} ], "Player": [ {"name": "wallet", "type": "address"}, {"name": "email", "type": "string"} ], "Message": [ {"name": "player", "type": "Player"} ] }, "primaryType": "Message", "domain": { "chainId": 0, "name": "", "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", "version": "" } } )
編輯:我注意到編碼消息標題的差異。Ethers.js 將域編碼為:
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cccccccccccccccccccccccccccccccccccccccc
,而eip712
(和eth_account
)將其編碼為:0x03fdd899fb21de70ced90f32a1783b1f2014b8b662f8233df477aeefb0ff0e4a0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470000000000000000000000000ccccccccccccccccccccccccccccccccccccccccc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
以上似乎是編碼順序與對主要類型進行不同編碼的差異。
編輯:注意到消息標頭編碼的差異,並註意到只需要 EIP712 域中的一個欄位,我設法使用標頭中的單個值實現相同的編碼。即,我只使用,而不是使用所有的 :
chainId
、和,這在編碼中實現了奇偶校驗。因此,這表明要解決兩者之間的差異,必須進行一些排序。無論是側面還是側面。Given已經進行了欄位名稱排序,並且由於它在客戶端更廣泛地使用,因此在 Python 端修復排序是有意義的。verifyingContract``name``version``name``ether.js``python``ethers.js
編輯:編碼和擁有身體似乎沒有問題,它們匹配。因此,我可以在 Python 端檢索正確的簽名者。
編輯:我製作了一個複制上述所有內容的倉庫,您可以在此送出中找到它:https ://github.com/nazariyv/eip712-discrepancy/commit/c7baa4630cbb5f1d01b5f123ea0727d527fb25c4
您無法檢索到正確簽名者的原因是 EIP712Domain 編碼的差異。似乎因為
ethers.js
對類型進行排序,但 Python 沒有,所以你得到了不同的編碼頭,這會導致問題。消息的實際值沒有問題,至少在這個例子中沒有(那裡可能仍然存在問題)。這不是一個真正的解決方案,但對我來說就足夠了:刪除 EIP712Domain 上的所有欄位,只使用名稱。
為了
eip_712.js
const domain = { //chainId: 0, name: 'RKL Player Data', //verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', //version: '' };
為了
eip_712.py
class Message(EIP712Message): _name_: "string" = "RKL Player Data" # _chainId_: "uint256" = 0 # noqa # _name_: "string" = "" # noqa # _verifyingContract_: "address" = ( # noqa # "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" # ) # _version_: "string" = "" # noqa player: Player
並且您將在消息中實現奇偶校驗,因此將能夠正確恢復簽名者。
編輯:確實,問題在於 Python 中標頭欄位的排序。如果您將排序更改為您在此處看到的,您可以在 Pyhton 端恢復發件人沒問題:https ://github.com/nazariyv/eip712-discrepancy/tree/fix