首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >请求简单EIP712验证方案的代码评审

请求简单EIP712验证方案的代码评审
EN

Stack Overflow用户
提问于 2022-05-25 17:03:45
回答 1查看 584关注 0票数 1

你好,

我正在研究一个简单的EIP712白名单成员钱包注册/验证计划。简单地说,(符号类型的数据->传递到链,->提取签名地址->,而不是存储在链上的签名地址)。

我已经有一段时间把头撞在上面了。,我无法让在线链接提取的地址与签名的公共地址相匹配。,我的眼睛离这个问题太近了,我需要帮助寻找一些我可能错过的东西。以我最好的能力,我似乎是在坚持标准,但显然我做错了什么。

我指的是EIP712标准'Mail‘EIP参考实现这里(溶胶) + 这里(js)msfeldstein参考实现这里(溶胶) + 这里(ts)

约束

  • 出于某些原因,我不希望使用任何框架/OpenZeppelin(我也尝试过,但同样无法工作)。

Notes

  • 下面介绍的代码基本上是EIP参考实现,使其尽可能显式化,使故障排除/评审过程尽可能简单。同样,我也删除了所有其他测试console.logs。
  • 我的方法是通过运行v和打印到控制台来生成rs和签名公共地址。然后,我将.sol部署到Remix,并手动输入生成的值。
  • 同样,我也会将这个问题张贴在etc Exchange等网站上。
  • 替代类型化-数据签名方法/策略非常受欢迎。

如果您有时间和知识,我将感谢您对我的EIP712标准的实施情况的审查如下。

客户端:

代码语言:javascript
复制
// using ethereumjs-util 7.1.3
const ethUtil = require('ethereumjs-util');

// using ethereumjs-abi 0.6.9
const abi = require('ethereumjs-abi');


// The purpose of this script is to be painfully explicit for the sake
// of showing work, to ask for help.


// generate keys

prikey = ethUtil.keccakFromString('cow', 256);
signingAddress = ethUtil.privateToAddress(prikey);
    // 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826

// data

const typedData = {
    types: {
        EIP712Domain: [
            { name: 'name', type: 'string' },
            { name: 'version', type: 'string' },
            { name: 'chainId', type: 'uint256' },
            { name: 'verifyingContract', type: 'address' },
        ],
        Validation: [
            { name: 'wallet', type: 'address' },
            { name: 'share', type: 'uint256' },
            { name: 'pool', type: 'uint8' }
        ],
    },
    primaryType: 'Validation',
    domain: {
        name: 'Validator',
        version: '1',
        chainId: 1,
        verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
    },
    message: {
        wallet: '0xeeBA65D9C7E5832918d1F4277DE0a78b78efEC43',
        share: 1000,
        pool: 5,
    },
};

// create domain struct hash

const encodedDomainType = 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)';
const domainTypeHash = ethUtil.keccakFromString(encodedDomainType, 256);

var encTypes = [];
var encValues = [];

        // add typehash
        encTypes.push('bytes32');
        encValues.push(domainTypeHash);

        // add name
        encTypes.push('bytes32');
        encValues.push(ethUtil.keccakFromString(typedData.domain.name, 256));

        // add version
        encTypes.push('bytes32');
        encValues.push(ethUtil.keccakFromString(typedData.domain.version, 256));

        // add chainId
        encTypes.push('uint256');
        encValues.push(typedData.domain.chainId);
    
        // add chainId
        encTypes.push('address');
        encValues.push(typedData.domain.verifyingContract);

    // computer final hash
    domainStructHash = abi.rawEncode(encTypes, encValues);

// create validation struct hash
    
const encodedValidationType = 'Validation(address wallet,uint256 share,uint256 pool)';
const validationTypeHash = ethUtil.keccakFromString(encodedValidationType, 256);

encTypes = [];
encValues = [];

        // add typehash
        encTypes.push('bytes32');
        encValues.push(validationTypeHash);

        // add wallet address
        encTypes.push('address');
        encValues.push(typedData.message.wallet);

        // add share
        encTypes.push('uint256');
        encValues.push(typedData.message.share);

        // add pool
        encTypes.push('uint256');
        encValues.push(typedData.message.pool);

    // computer final hash
    validationStructHash = abi.rawEncode(encTypes, encValues);

// now finally create final signature hash

signatureHash = ethUtil.keccak256(
    Buffer.concat([
        Buffer.from('1901', 'hex'),
            domainStructHash,
            validationStructHash,
        ]),
    );

// and finally, sign

signature = ethUtil.ecsign(signatureHash, prikey);

// convert r, s, and signingAddress into hex strings to pass to remix

console.log(signature.v);

var r = ''
function pad2(s) {return s.length < 2 ? "0" + s : s}; 
    for(i = 0; i < signature.r.length; i++) {
        r += pad2(signature.r[i].toString(16)); }
console.log('0x' + r); // r bytes

var s = ''
function pad2(s) {return s.length < 2 ? "0" + s : s}; 
    for(i = 0; i < signature.s.length; i++) {
        s += pad2(signature.s[i].toString(16)); }
console.log('0x' + s); // s bytes

var str = '';
function pad2(s) {return s.length < 2 ? "0" + s : s};
    for(i = 0; i < signingAddress.length; i++) {
        str += pad2(signingAddress[i].toString(16)); }
console.log('0x' + str); // signingAddress bytes

链上的

代码语言:javascript
复制
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

contract validateData {

    address _validationKey = 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826;

    struct EIP712Domain {
        string  name;
        string  version;
        uint256 chainId;
        address verifyingContract;
    }

    struct Validation {
        address wallet;
        uint256 share;
        uint256 pool;
    }

    bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    );

    bytes32 constant VALIDATION_TYPEHASH = keccak256(
        "Validation(address wallet,uint256 share,uint256 pool)"
    );

    bytes32 DOMAIN_SEPARATOR;

    constructor () {
        DOMAIN_SEPARATOR = hash(EIP712Domain({
            name: "Validator",
            version: '1',
            chainId: 1,
            verifyingContract: 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
        }));
    }

    function hash(EIP712Domain memory eip712Domain) internal pure returns (bytes32) {
        return keccak256(abi.encode(
            EIP712DOMAIN_TYPEHASH,
            keccak256(bytes(eip712Domain.name)),
            keccak256(bytes(eip712Domain.version)),
            eip712Domain.chainId,
            eip712Domain.verifyingContract
        ));
    }

    function hash(Validation calldata validation) internal pure returns (bytes32) {
        return keccak256(abi.encode(
            VALIDATION_TYPEHASH,
            validation.wallet,
            validation.share,
            validation.pool
        ));
    }

    event compare(address sig, address key);

    function verify(Validation calldata validation, uint8 v, bytes32 r, bytes32 s) public {
        bytes32 digest = keccak256(abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR,
            hash(validation)
        ));
        emit compare(ecrecover(digest, v, r, s), _validationKey);
    }
    
}

谢谢你的时间和考虑!

EN

回答 1

Stack Overflow用户

发布于 2022-07-25 12:59:13

当您成功地检索到地址时,请使用此契约模板首先恢复您的地址,然后将其应用到您的合同中。我也会解释一下合同:

代码语言:javascript
复制
pragma solidity ^0.8.0;

contract SignTest {

address owner = msg.sender;

mapping(uint256 => bool) usedNonces;

function test(uint256 amount, uint256 nonce, bytes memory sig, uint tV, bytes32 tR, bytes32 tS, bytes32 tMsg) public view returns(address) {

    bytes32 message = prefixed(keccak256(abi.encodePacked(amount, nonce))); 
    bytes32 messageWithoutPrefix = keccak256(abi.encodePacked(amount, nonce)); 

   
    address signer = recoverSigner(messageWithoutPrefix, sig, tV, tR,tS);

    return signer;
}

// Signature methods

function splitSignature(bytes memory sig)
    public
    view
    returns (uint8, bytes32, bytes32)
{
    require(sig.length == 65, "B");

    bytes32 r;
    bytes32 s;
    uint8 v;

    assembly {
        // first 32 bytes, after the length prefix
        r := mload(add(sig, 32))
        // second 32 bytes
        s := mload(add(sig, 64))
        // final byte (first byte of the next 32 bytes)
        v := byte(0, mload(add(sig, 96)))
    }

    return (v, r, s);
}

function recoverSigner(bytes32 message, bytes memory sig, uint tV, bytes32 tR, bytes32 tS)
    public
    view
    returns (address)
{
    uint8 v;
    bytes32 r;
    bytes32 s;

    (v, r, s) = splitSignature(sig);

    require(v==tV, "V is not correct");
    require(r==tR, "R is not correct");
    require(s==tS, "S is not correct");

    return ecrecover(message, v, r, s);
}

// Builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 inputHash) public pure returns (bytes32) {
    return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inputHash));
}

}

我使用web3来恢复,因此,让我提供一个简单的示例:

代码语言:javascript
复制
    let fnSignature = web3.utils.keccak256("setApprovalForAll(address,bool").substr(0,10)

// encode the function parameters and add them to the call data
let fnParams = web3.eth.abi.encodeParameters(
  ["address","bool"],
  [toAddr,permit]
)

calldata = fnSignature + fnParams.substr(2)

console.log(calldata)

首先,对函数签名的前4个字节进行substr,然后将它们编码到fnParams + fnSignature中。

第二步是签署您的数据:

代码语言:javascript
复制
const data = calldata //Retrieved from above code
const NFTAddress = 'Contract address where you sign'
const newSigner = web3.eth.accounts.privateKeyToAccount("Your Priv Key");
const myAccount = web3.eth.accounts.wallet.add(newSigner);
const signer = myAccount.address;
console.log(signer) // display the target address in console ( for better verification )

使用第一个函数中的回调数据并将其添加到(您希望在其中签名散列),然后按照以下步骤操作:

代码语言:javascript
复制
    let rawData = web3.eth.abi.encodeParameters(
    ['address','bytes'],
    [NFTAddress,data]
  );
  // hash the data.
  let hash = web3.utils.soliditySha3(rawData);
  console.log(hash)
  // sign the hash.
  let signature = web3.eth.sign(hash, signer);
 console.log(signature)

使用“散列”,转到实体契约(由我发布),并使用“签名”将检索到的字节(散列)添加到函数前缀(bytes32散列)中,转到实体契约并检索v,r,s。使用函数splitSignature(bytes32签名)使用由函数前缀生成的字节( bytes32散列),使用函数recoverSigner(消息(前缀字节(Bytes32hash)),签名(来自web3 javascript),v( uint8 from splitSignature),r(Bytes32 from splitSignature),s(bytes32,splitSignature) )。

你也可以玩我提供的合同和脚本,但是,我更喜欢一步一步的发布指南,让每个人都能理解。

发展愉快!:)

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72381687

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档