您的位置:首页 > 区块链 >

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