在solidity中方验证椭圆曲线签名智能合约代码

Solidity中恢复消息签名者地址:

  一般来说,ECDSA签名由rs两个参数组成。以太坊中的签名包括名为v的第三个参数,可以使用它来验证哪个帐户的私钥用于对消息进行签名,以及交易的发送者。Solidity提供了一个内置函数ecrecover,它接受消息message以及rsv参数,并返回用于对消息签名的地址。

提取签名参数rsv

  web3.js生成的签名是r, sv的串联,所以第一步是将这些参数分开。可以在客户端执行此操作,但在智能合约内部将这些参数分开意味着需要发送一个签名参数message(已签名)而不是三个参数rsvsolidity语言中将字节数组message拆分为三个参数rsv很困难,因此我们使用内联汇编(inline assembly)来完成下述函数中的工作splitSignature(本节末尾完整合约中的第三个函数).

计算消息哈希值:

  智能合约需要确切知道签署了哪些参数,因此它必须从参数重新创建消息并将其用于签名验证。函数prefixed函数recoverSigner合约claimPayment实现此操作。

完整代码如下(附详细注释):

 

pragma solidity >=0.7.0 <0.9.0;

contract ReceivePays{

    address owner = msg.sender;//存储调用合约者的地址

    //mapping类似于散列表和字典,只能声明为状态变量,不支持迭代,支持嵌套
    mapping(uint256 => bool) usedNonces;//记录nonce的使用情况,标识nonce的唯一性

    //构造函数,可为空,部署合约时调用,仅调用一次
    constructor() payable{}

    function claimPayment(uint256 amount,uint256 nonce,bytes memory signature) external{
        //require()中判断条件为true则继续,为false则退出该function,回退该function内所有更改
        require(!usedNonces[nonce]);//判断当前传入nonce是否被使用过

        usedNonces[nonce] = true;

        //abi.encodePacked(...) returns (bytes memory)
        //this(当前合约类型):当前function调用者地址,可显式转换为address或address payable类型
        //对message进行椭圆曲线加密
        bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender,amount,nonce,this)));

        //验证签名signature是否和加密消息message所返回的公钥地址相同,即验证签名的正确性
        require(recoverSigner(message,signature) == owner);

        //签名正确性验证通过后转账
        payable(msg.sender).transfer(amount);
    }

    //selfdestruct(address payable recipient):销毁当前合约,将其资金发送到给定地址
    function shutdown() external{
        require(msg.sender == owner);
        selfdestruct(payable(msg.sender));
    }

    //assembly{}为solidity设置的内联汇编语言,用于以一种底层方式访问EVM虚拟机
    // := 是内联汇编语言语法,且solidity支持return多个返回值,用括号括起来即可
    //mload()是内联汇编语言中的操作码,类似于封装好的函数直接调用即可,mload(p)表示mem[p...(p+32)]
    //mem[a...b)表示将位置a到位置b的memory字节内容分离出来,add(a,b)表示a+b
    function splitSignature(bytes memory sig) internal pure returns(uint8 v,bytes32 r,bytes32 s){
        require(sig.length == 65);
        assembly{
            r:=mload(add(sig,32))
            s:=mload(add(sig,64))
            v:=byte(0,mload(add(sig,96)))
        }
        return (v,r,s);
    }

    //ecrecover(bytes32 hash,uint8 v,bytes32 r,bytes32 s) returns (address)
    //从椭圆曲线签名中恢复与公钥关联的地址,错误返回零
    function recoverSigner(bytes32 message,bytes memory sig) internal pure returns(address){
        (uint8 v,bytes32 r,bytes32 s) = splitSignature(sig);
        return ecrecover(message,v,r,s);
    }

    //以太坊有两种信息传递,一种是交易(涉及转账,外部账户与合约账户之间,或,外部账户与外部账户之间的消息传递)
    //另一种是消息(指合约与合约之间的消息传递),对这两种信息加密调用的函数不同,但同样的输入可能会有同样的输出
    //为避免这种碰撞(即两种编码得到的结果相同),为解决这种情况,选择在对消息加密时
    //格式为("\x19Ethereum Signed Message:\n" + len(message),message)
    //如下所示,此处的len(hash)=32,而对交易加密时,不作特殊处理
    //这解释了上文中claimPayment中的prefixed(keccak256(abi.encodePacked(msg.sender,amount,nonce,this)))没有加前缀的原因
    function prefixed(bytes32 hash) internal pure returns(bytes32){
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",hash));
        //keccak256()椭圆曲线加密,abi.encodePacked()对输入数据编码
    }

}

 

 来源(solidity官方英文文档0.8.13):https://docs.soliditylang.org/en/v0.8.13/solidity-by-example.html#recovering-the-message-signer-in-solidity

solidity官方中文文档0.8.0:https://learnblockchain.cn/docs/solidity/solidity-by-example.html#id13

其他知识解释:

(1)为什么签名前要加"\x19Ethereum Signed Message:\n":https://www.cnblogs.com/wanghui-garcia/p/9642492.html

(2)内联汇编(inline assembly)简介及其语法:https://blog.csdn.net/shjuzhen/article/details/80941432

(3)keccak256():https://docs.soliditylang.org/en/v0.8.13/cheatsheet.html#global-variables

(4)abi.encodePacked():https://docs.soliditylang.org/en/v0.8.13/abi-spec.html#non-standard-packed-mode

(5)ecrecover():https://docs.soliditylang.org/en/v0.8.13/cheatsheet.html#global-variables

 

posted @ 2022-04-09 11:40  豆豆是只乖狗狗  阅读(696)  评论(0编辑  收藏  举报