您的位置:首頁 > 區塊鏈 >

        QIP-6減小智能合約的開發成本 完備Qtum的智能合約系統

        2019-09-26 14:12:41 來源: 區塊網

        背 景以太坊中的ecrecover函數可以用來獲取對一條消息簽名的地址。這對于證明一條消息或者一段數據被一個指定的賬戶簽名過(而不是被篡改過)

        背 景

        以太坊中的ecrecover函數可以用來獲取對一條消息簽名的地址。這對于證明一條消息或者一段數據被一個指定的賬戶簽名過(而不是被篡改過)非常有用。但是 Qtum 沒有使用以太坊的賬戶模型,而是采用比特幣的 UTXO 模型,地址的算法也和以太坊不同,因此這個函數并不適用于 Qtum。

        在一些需要驗證簽名來源信息的情況下, Qtum 開發者并不能方便的在智能合約中完成這個驗證,而是需要在合約中完整實現或者調用一次從簽名和消息獲取簽名者公鑰的合約,會造成非常大的開銷,進而使得相應合約的調用費用非常高。

        問題的細節

        ecrecover接受一個消息的哈希和消息的簽名,然后計算出簽名的私鑰對應的公鑰,并將該公鑰轉換為以太坊地址格式。然而以太坊的地址算法和 Qtum 不同,而且ecrecover返回的是公鑰經過哈希以后的結果,這個過程不可逆,因此在 Qtum 上無法使用這個函數。

        在以太坊中,地址計算方法如下:

        keccak256(pubkey)

        而在 Qtum 上,地址的計算方式和比特幣相同,使用如下計算方法:

        ripemd160(sha256(pubkey))

        在 Qtum 的合約中,msg.sender是一個 Qtum 地址。由于從公鑰開始轉換為地址的每一步操作都是不可逆的,ecrecover返回的以太坊地址無法和msg.sender中的 Qtum 地址進行比較。而現有的 Qtum 智能合約中并沒有提供任何函數來從消息簽名中獲取 Qtum 地址,這導致 Qtum 智能合約開發者們不得不開發或使用Secp256k1相關的庫來計算簽名公鑰和地址,造成更大的計算開銷和更高的合約費用。

        另一個需要注意的細節是,Qtum 沿用的比特幣消息簽名算法和以太坊的消息簽名算法的實現上有一些細微的差別:

        以太坊的簽名按如下格式組成:

        [r][s][v]

        而 Qtum 的簽名則是:

        [v][r][s]

        其中v是 recover id,r是橢圓曲線上的一個點R的X坐標,s是這個點R的Y坐標。如上的不同導致 Qtum 和以太坊的 recover 算法的實現細節也不相同。

        QIP-6 的解決方案

        通過在 Qtum 的虛擬機中增加一個預編譯的合約,以提供一個用來調用 Qtum 核心代碼中的 recover 代碼的接口。智能合約開發者只需要寫簡單的一兩個函數就能從簽名消息中獲取到簽名者的地址。新增的預編譯合約的接口和ecrecover保持一致。

        什么是預編譯合約

        預編譯合約是 EVM 中為了提供一些不適合寫成 opcode 的較為復雜的庫函數(多數用于加密、哈希等復雜計算)而采用的一種折中方案。由于它是用底層代碼實現的,執行速度快,對于開發者來說就比直接用運行在 EVM 上的函數消耗更低。以太坊中使用預編譯合約提供一些常用的較為繁瑣的操作,比如sha256、ripemd160hash等。

        預編譯合約的實現

        預編譯合約的核心代碼由虛擬機底層(C++)實現,通過在虛擬機的初始化過程中注冊到人為指定的固定地址上來提供智能合約調用的接口。

        預編譯合約的使用

        一個典型的調用方式:

        assembly {

        if iszero(call(gasLimit, contractAddress, value, input, inputLength, output, outputLength)) {

        revert(0, 0)

        }

        }

        在新版本的虛擬機中,還可以使用staticcall:

        assembly {

        success := staticcall(gasLimit, contractAddress, input, inputLength, output, outputLength)

        }

        其中contractAddress就是要調用的預編譯合約的地址,本次 Qtum 新增的 btc_ecrecover 的地址是0x85。input是調用合約的參數列表。這個調用的返回值代表了調用是否成功,1表示成功,0表示失敗。而返回的數據會寫入到output里面。

        下面我們看一個例子:

        pragma solidity ^0.5.0;

        /**

        * @title Elliptic curve signature operations

        * @dev Based on

        https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d

        * TODO Remove this library once solidity supports passing a signature to ecrecover.

        * See https://github.com/ethereum/solidity/issues/864

        */

        library ECDSA {

        /**

        * @dev Recover signer address from a message by using their signature.

        * @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.

        * @param signature bytes signature, the signature is generated using web3.eth.sign()

        */

        function recover(bytes32 hash, bytes memory signature) internal view returns (address) {

        // Check the signature length

        if (signature.length != 65) {

        return (address(0));

        }

        // Divide the signature in r, s and v variables

        bytes32 r;

        bytes32 s;

        uint8 v;

        // ecrecover takes the signature parameters, and the only way to get them

        // currently is to use assembly.

        // solhint-disable-next-line no-inline-assembly

        assembly {

        v := byte(0, mload(add(signature, 0x20)))

        r := mload(add(signature, 0x21))

        s := mload(add(signature, 0x41))

        }

        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature

        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines

        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most

        // signatures from current libraries generate a unique signature with an s-value in the lower half order.

        //

        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value

        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or

        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept

        // these malleable signatures as well.

        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {

        return address(0);

        }

        // Support both compressed or uncompressed

        if (v != 27 && v != 28 && v != 31 && v != 32) {

        return address(0);

        }

        // If the signature is valid (and not malleable), return the signer address

        return btc_ecrecover(hash, v, r, s);

        }

        function btc_ecrecover(bytes32 msgh, uint8 v, bytes32 r, bytes32 s) public view returns(address) {

        uint256[4] memory input;

        input[0] = uint256(msgh);

        input[1] = v;

        input[2] = uint256(r);

        input[3] = uint256(s);

        uint256[1] memory retval;

        uint256 success;

        assembly {

        success := staticcall(not(0), 0x85, input, 0x80, retval, 32)

        }

        if (success != 1) {

        return address(0);

        }

        return address(retval[0]);

        }

        }

        在上面這個例子中,只要調用btc_ecrecover函數就能獲取到消息簽名者的地址。為了簡化輸入,例子中也封裝了一個recover函數,使得開發者只要傳入原始簽名就能完成合約調用。

        下面我們不使用預編譯合約,而是完全使用 solidity 代碼實現 btc_ecrecover功能。代碼實現如下:

        pragma solidity ^0.4.26;

        import {ECCMath} from "github.com/androlo/standard-contracts/contracts/src/crypto/ECCMath.sol";

        import {Secp256k1} from "github.com/androlo/standard-contracts/contracts/src/crypto/Secp256k1.sol";

        library ECDSA {

        uint256 constant p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f;

        uint256 constant n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;

        uint256 constant gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798;

        uint256 constant gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8;

        function recover(bytes32 hash, bytes memory signature) internal view returns (address) {

        if (signature.length != 65) {

        return (address(0));

        }

        bytes32 r;

        bytes32 s;

        uint8 v;

        assembly {

        v := byte(0, mload(add(signature, 0x20)))

        r := mload(add(signature, 0x21))

        s := mload(add(signature, 0x41))

        }

        if (uint256(s) > n / 2) {

        return address(0);

        }

        if (v != 27 && v != 28 && v != 31 && v != 32) {

        return address(0);

        }

        return btc_ecrecover(hash, v, r, s);

        }

        function btc_ecrecover(bytes32 msgh, uint8 v, bytes32 r, bytes32 s) public view returns (address) {

        uint i = 0;

        uint256 rr = uint256(r);

        uint256 ss = uint256(s);

        bool isYOdd = ((v - 27) & 1) != 0;

        bool isSecondKey = ((v - 27) & 2) != 0;

        bool isCompressed = ((v - 27) & 4) != 0;

        if (rr >= p % n && isSecondKey) {

        return address(0);

        }

        uint256[3] memory P = _getPoint(uint256(msgh), rr, ss, isYOdd, isSecondKey);

        if (P[2] == 0) {

        return address(0);

        }

        ECCMath.toZ1(P, p);

        bytes memory publicKey;

        if (isCompressed) {

        publicKey = new bytes(33);

        publicKey[0] = byte(P[1] % 2 == 0 ? 2 : 3);

        for (i = 0; i < 32; ++i) {

        publicKey[32 - i] = byte((P[0] >> (8 * i)) & 0xff);

        }

        } else {

        publicKey = new bytes(65);

        publicKey[0] = 4;

        for (i = 0; i < 32; ++i) {

        publicKey[32 - i] = byte((P[0] >> (8 * i)) & 0xff);

        publicKey[64 - i] = byte((P[1] >> (8 * i)) & 0xff);

        }

        }

        return address(ripemd160(sha256(publicKey)));

        }

        function _getPoint(uint256 msgh, uint256 r, uint256 s, bool isYOdd, bool isSecondKey) internal view returns (uint256[3] memory) {

        uint256 rx = isSecondKey ? r + n : r;

        uint256 ry = ECCMath.expmod(ECCMath.expmod(rx, 3, p) + 7, p / 4 + 1, p);

        if (isYOdd != (ry % 2 == 1)) {

        ry = p - ry;

        }

        uint256 invR = ECCMath.invmod(r, n);

        return Secp256k1._add(

        Secp256k1._mul(n - mulmod(msgh, invR, n), [gx, gy]),

        Secp256k1._mul(mulmod(s, invR, n), [rx, ry])

        );

        }

        }

        我們在測試鏈上部署了上述兩個兩個合約,地址分別如下:

        預編譯合約: 21ea1d8376d1820d7091084a76f380143b59aaf8

        solidity實現: 4fdff1b4bde5edf13360ff0946518a01115ce818

        使用地址

        qQqip6i2e2buCZZNdqMw4VNpaYpnLm4JAx對消息btc_ecrecover test進行簽名,我們得到btc_ecrecover 的調用參數:

        bytes32 msgh = 0xdfa80e3294fd8806ab908904403db376b3dd35c6356ab2d3b884db4f6ec5e93d

        uint8 v = 0x20

        bytes32 r = 0xca08c0813407de3a78053c976462eacbde3fd69843e21acf8dd636149bf4b753

        bytes32 s = 0x0731bce3ed9b489da0165af79759c1d586ef8fe53b3aab95fcab68d01ed6f156

        兩個合約調用調用結果如下:

        1.預編譯合約

        callcontract 21ea1d8376d1820d7091084a76f380143b59aaf8 69bc0963dfa80e3294fd8806ab908904403db376b3dd35c6356ab2d3b884db4f6ec5e93d0000000000000000000000000000000000000000000000000000000000000020ca08c0813407de3a78053c976462eacbde3fd69843e21acf8dd636149bf4b7530731bce3ed9b489da0165af79759c1d586ef8fe53b3aab95fcab68d01ed6f156

        {

        "address": "21ea1d8376d1820d7091084a76f380143b59aaf8",

        "executionResult": {

        "gasUsed": 32688,

        "excepted": "None",

        "newAddress": "21ea1d8376d1820d7091084a76f380143b59aaf8",

        "output": "0000000000000000000000004fdff1b4bde5edf13360ff0946518a01115ce818",

        "codeDeposit": 0,

        "gasRefunded": 0,

        "depositSize": 0,

        "gasForDeposit": 0,

        "exceptedMessage": ""

        },

        "transactionReceipt": {

        "stateRoot": "5d9e1ad1b5d09e9e7c41d09078434488927366adf8ebf5a0049bb99610a817f1",

        "gasUsed": 32688,

        "bloom": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",

        "log": []

        }

        }

        2.solidity 實現

        callcontract d3764a0b7fbbe2e39ee4adc3908b5b5dbea22c14 69bc0963dfa80e3294fd8806ab908904403db376b3dd35c6356ab2d3b884db4f6ec5e93d0000000000000000000000000000000000000000000000000000000000000020ca08c0813407de3a78053c976462eacbde3fd69843e21acf8dd636149bf4b7530731bce3ed9b489da0165af79759c1d586ef8fe53b3aab95fcab68d01ed6f156

        {

        "address": "d3764a0b7fbbe2e39ee4adc3908b5b5dbea22c14",

        "executionResult": {

        "gasUsed": 886077,

        "excepted": "None",

        "newAddress": "d3764a0b7fbbe2e39ee4adc3908b5b5dbea22c14",

        "output": "0000000000000000000000004fdff1b4bde5edf13360ff0946518a01115ce818",

        "codeDeposit": 0,

        "gasRefunded": 0,

        "depositSize": 0,

        "gasForDeposit": 0,

        "exceptedMessage": ""

        },

        "transactionReceipt": {

        "stateRoot": "5d9e1ad1b5d09e9e7c41d09078434488927366adf8ebf5a0049bb99610a817f1",

        "gasUsed": 886077,

        "bloom": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",

        "log": []

        }

        }

        可見預編譯合約調用btc_ecrecover需要花費 32688 gas,而完全用 solidity 實現需要 886077 gas。預編譯合約實現的 gas 花費遠遠少于 solidity 實現。

        QIP-6 的影響

        QIP-6 大大減小了智能合約開發者的開發成本。從調用合約的角度來說,如果完全用 solidity 在合約中實現 recover,其 gas 使用量遠遠超過btc_ecrecover函數。于是使用btc_ecrecover來獲取消息簽名地址的合約調用成本也大大降低。此外,QIP-6 也讓 Qtum 的智能合約系統更加完備。

        另一方面,QIP-6 沒有對原有的ecrecover進行修改,保持了 Qtum 和以太坊的兼容性,理論上不會帶來任何風險。(Qtum量子鏈)

        關鍵詞: QIP-6 智能合約 Qtum

        精選 導讀

        募資55億港元萬物云啟動招股 預計9月29日登陸港交所主板

        萬科9月19日早間公告,萬物云當日啟動招股,預計發行價介乎每股47 1港元至52 7港元,預計9月29日登陸港交所主板。按發行1 167億股計算,萬

        發布時間: 2022-09-20 10:39
        管理   2022-09-20

        公募基金二季度持股情況曝光 隱形重倉股多為高端制造業

        隨著半年報披露收官,公募基金二季度持股情況曝光。截至今年二季度末,公募基金全市場基金總數為9794只,資產凈值為269454 75億元,同比上

        發布時間: 2022-09-02 10:45
        資訊   2022-09-02

        又有上市公司宣布變賣房產 上市公司粉飾財報動作不斷

        再有上市公司宣布變賣房產。四川長虹25日稱,擬以1 66億元的轉讓底價掛牌出售31套房產。今年以來,A股公司出售房產不斷。根據記者不完全統

        發布時間: 2022-08-26 09:44
        資訊   2022-08-26

        16天12連板大港股份回復深交所關注函 股份繼續沖高

        回復交易所關注函后,大港股份繼續沖高。8月11日大港股份高開,隨后震蕩走高,接近收盤時觸及漲停,報20 2元 股。值得一提的是,在7月21日

        發布時間: 2022-08-12 09:56
        資訊   2022-08-12

        萬家基金再添第二大股東 中泰證券擬受讓11%基金股權

        7月13日,中泰證券發布公告,擬受讓齊河眾鑫投資有限公司(以下簡稱齊河眾鑫)所持有的萬家基金11%的股權,交易雙方共同確定本次交易的標的資

        發布時間: 2022-07-14 09:39
        管理   2022-07-14

        央行連續7日每天30億元逆回購 對債市影響如何?

        央行12日再次開展了30億元逆回購操作,中標利率2 10%。這已是央行連續7日每天僅進行30億元的逆回購縮量投放,創下去年1月以來的最低操作規

        發布時間: 2022-07-13 09:38
        資訊   2022-07-13

        美元指數創近20年新高 黃金期貨創出逾9個月新低

        由于對美聯儲激進加息的擔憂,美元指數11日大漲近1%創出近20年新高。受此影響,歐美股市、大宗商品均走弱,而黃金期貨創出逾9個月新低。美

        發布時間: 2022-07-13 09:36
        資訊   2022-07-13

        美股三大股指全線下跌 納斯達克跌幅創下記錄以來最大跌幅

        今年上半年,美股持續回落。數據顯示,道瓊斯指數上半年下跌15 3%,納斯達克綜合指數下跌29 5%,標普500指數下跌20 6%。其中,納斯達克連續

        發布時間: 2022-07-04 09:51
        推薦   2022-07-04

        融資客熱情回升 兩市融資余額月內增加超344億元

        近期A股走強,滬指6月以來上漲4%,融資客熱情明顯回升。數據顯示,截至6月16日,兩市融資余額1 479萬億元,月內增加344 67億元,最近一個半

        發布時間: 2022-06-20 09:41
        資訊   2022-06-20

        4個交易日凈買入超百億元 北向資金持續流入A股市場

        北向資金凈流入態勢延續。繼6月15日凈買入133 59億元后,北向資金6月16日凈買入44 52億元。自5月27日至今,除6月13日以外,北向資金累計凈

        發布時間: 2022-06-17 09:37
        推薦   2022-06-17