1. 程式人生 > 其它 >在solidity中方驗證橢圓曲線簽名智慧合約程式碼

在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