constkeyId='1';constrequestPayload:RequestPayload<TransactionParams>={method:'eth_sendTransaction',params:[{/* ... */},],};constsignature:`0x${string}`=awaitgetSignature(requestPayload,keyId);// Using the EIP-1193 provider interfaceconstresult=awaitethereum.request({method:'wallet_signedRequest',params:[requestPayload,signature,keyId],});
签名验证
收到 EIP-1193 调用后,钱包 MUST 检查 sender.tab.url 域的 TWIT manifest 是否存在
a. 钱包 MUST 验证 manifest 是否托管在 sender.tab.url 域上
b. 钱包 SHOULD 查找 DNS TXT 记录以找到 manifest 位置
b. 钱包 MAY 首先尝试 /.well-known/twit.json 位置
如果未为 sender.tab.url 域配置 TWIT,则照常进行
如果配置了 TWIT 且使用了 request 方法,则钱包 SHOULD 向用户显示可见且可操作的警告
a. 如果用户选择忽略警告,则照常进行
b. 如果用户选择取消,则钱包 MUST 取消调用
如果配置了 TWIT 并且使用了 wallet_signedRequest 方法,参数为 requestPayload、signature 和 keyId,则:
a. 钱包 MAY 显示可见的提示,表明此交互已签名
b. 钱包 MUST 验证 keyId 是否存在于 TWIT manifest 中,并找到关联的密钥记录
c. 从密钥记录中,钱包 MUST 使用 alg 字段和 publicKey 字段,通过调用 crypto.verify(alg, key, signature, requestPayload) 来验证 requestPayload 的完整性
d. 如果签名无效,钱包 MUST 向用户显示可见且可操作的警告
i. 如果用户选择忽略警告,则继续使用参数 requestPayload 调用 request
ii. 如果用户选择取消,则钱包 MUST 取消调用
e. 如果签名有效,钱包 MUST 使用参数 requestPayload 调用 request
示例方法实现(钱包)
asyncfunctionsignedRequest(requestPayload:RequestPayload<unknown>,signature:`0x${string}`,keyId:string,):Promise<unknown>{// 1. Get the domain of the sender.tab.url// 1. 获取 sender.tab.url 的域constdomain=getDappDomain();// 2. Get the manifest for the current domain// 2. 获取当前域的 manifest// It's possible to use RFC 8484 for the actual DNS-over-HTTPS specification, see https://datatracker.ietf.org/doc/html/rfc8484.// 可以使用 RFC 8484 作为实际的基于 HTTPS 的 DNS 规范,请参阅 https://datatracker.ietf.org/doc/html/rfc8484。// However, here we are doing it with DoHjs.// 但是,这里我们使用 DoHjs 来实现。// This step is optional, and you could go directly to the well-known address first at `domain + '/.well-known/twit.json'`// 此步骤是可选的,您可以首先直接转到众所周知的地址 `domain + '/.well-known/twit.json'`constdoh=require('dohjs');constresolver=newdoh.DohResolver('https://1.1.1.1/dns-query');letmanifestPath='';constdnsResp=awaitresolver.query(domain,'TXT');for(recordofdnsResp.answers){if(!record.data.startsWith('TWIT='))continue;manifestPath=record.data.substring(5);// This should be domain + '/.well-known/twit.json'// 这应该是 domain + '/.well-known/twit.json'break;}// 3. Parse the manifest and get the key and algo based on `keyId`// 3. 解析 manifest 并根据 `keyId` 获取密钥和算法constmanifestReq=awaitfetch(manifestPath);constmanifest=awaitmanifestReq.json();constkeyData=manifest.publicKeys.filter((x)=>x.id==keyId);if(!keyData){thrownewError('Could not find the signing key');// 找不到签名密钥}constkey=keyData.publicKey;constalg=keyData.alg;// 4. Verify the signature// 4. 验证签名constvalid=awaitcrypto.verify(alg,key,signature,requestPayload);if(!valid){thrownewError('The data was tampered with');// 数据已被篡改}returnawaitprocessRequest(requestPayload);}