在Moonbase Alpha上的以太坊主网预编译合约¶
概览¶
Moonbase Alpha v2版本中新增的另外一个功能是提供以太坊上的一些原生预编译合约。
目前这一功能包含以下预编译:ecrecover、sha256、sha3FIPS256、ripemd-160、Bn128Add、Bn128Mul、Bn128Pairing、恒等函数和模幂运算函数。
本教程中,我们将介绍如何使用和/或验证这些预编译。
查看先决条件¶
本教程操作需安装Node.js(这个示例将使用v16.x)和「npm package manager」。您可通过Node.js下载或自行运行以下代码完成安装
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt install -y nodejs
# You can use homebrew (https://docs.brew.sh/Installation)
brew install node
# Or you can use nvm (https://github.com/nvm-sh/nvm)
nvm install node
您可以通过请求每个安装包的版本来验证是否安装正确:
node -v
npm -v
撰写本教程时,所用版本分别为15.2.1和7.0.8。另外,我们还将需要通过执行以下命令安装Web3安装包:
npm install --save web3
您需要使用ls
命令来验证所安装的Web3版本:
npm ls web3
要在Moonbeam或Moonriver网络上测试本指南中的示例,您可以从受支持的网络端点提供商之一获取您自己的端点和API密钥。
使用ECRECOVER进行签名验证¶
这一预编译的主要功能是验证消息签名。一般来说,将某笔交易的签名值输入到ecrecover
中,将会返回一个地址,如果该地址与发送该交易的公共地址相同,则签名通过验证。
我们用一个简单的例子来说明如何利用这一预编译功能。我们需要进行签名,然后获取包含这些数值的已签名消息,从而获得交易签名值(v, r, s):
const Web3 = require('web3');
// Provider
const web3 = new Web3('https://rpc.api.moonbase.moonbeam.network');
// Address and Private Key
const address = '0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b';
const pk1 = '99B3C12287537E38C90A9219D4CB074A89A16E9CDB20BF85728EBD97C343E342';
const msg = web3.utils.sha3('supercalifragilisticexpialidocious');
async function signMessage(pk) {
try {
// Sign and get Signed Message
const smsg = await web3.eth.accounts.sign(msg, pk);
console.log(smsg);
} catch (error) {
console.error(error);
}
}
signMessage(pk1);
这一代码将在终端返回以下对象:
{
message: '0xc2ae6711c7a897c75140343cde1cbdba96ebbd756f5914fde5c12fadf002ec97',
messageHash: '0xc51dac836bc7841a01c4b631fa620904fc8724d7f9f1d3c420f0e02adf229d50',
v: '0x1b',
r: '0x44287513919034a471a7dc2b2ed121f95984ae23b20f9637ba8dff471b6719ef',
s: '0x7d7dc30309a3baffbfd9342b97d0e804092c0aeb5821319aa732bc09146eafb4',
signature: '0x44287513919034a471a7dc2b2ed121f95984ae23b20f9637ba8dff471b6719ef7d7dc30309a3baffbfd9342b97d0e804092c0aeb5821319aa732bc09146eafb41b'
}
有了这些必要数值,我们就可以到Remix测试预编译合约。请注意,签名验证也可以通过Web3.js库来实现,但在本示例中,我们将会使用Remix,以确保它使用的是区块链上的预编译合约。我们可以使用以下Solidity代码进行签名验证:
pragma solidity ^0.7.0;
contract ECRECOVER{
address addressTest = 0x12Cb274aAD8251C875c0bf6872b67d9983E53fDd;
bytes32 msgHash = 0xc51dac836bc7841a01c4b631fa620904fc8724d7f9f1d3c420f0e02adf229d50;
uint8 v = 0x1b;
bytes32 r = 0x44287513919034a471a7dc2b2ed121f95984ae23b20f9637ba8dff471b6719ef;
bytes32 s = 0x7d7dc30309a3baffbfd9342b97d0e804092c0aeb5821319aa732bc09146eafb4;
function verify() public view returns(bool) {
// Use ECRECOVER to verify address
return (ecrecover(msgHash, v, r, s) == (addressTest));
}
}
使用Remix编译器部署并将MetaMask连接至Moonbase Alpha即可部署合约。调用verify()
方法进行验证,如果ecrecover
返回的地址与消息签名所使用的地址(与密钥相关,需在合约中手动设置)一致,即返回true。
使用SHA256函数获取哈希值¶
向这一函数输入数据可返回其SHA256哈希值。测试这一预编译合约,可以使用此在线工具来计算任何字符串的SHA256哈希值。在本示例中,我们将使用Hello World!
。直接进入Remix并部署以下代码,计算出来的哈希值将在expectedHash
变量中显示:
pragma solidity ^0.7.0;
contract Hash256{
bytes32 public expectedHash = 0x7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069;
function calculateHash() internal pure returns (bytes32) {
string memory word = 'Hello World!';
bytes32 hash = sha256(bytes (word));
return hash;
}
function checkHash() public view returns(bool) {
return (calculateHash() == expectedHash);
}
}
合约部署后,就可以调用checkHash()
方法进行验证。如果calculateHash()
返回的哈希值与所提供的哈希值一致,即返回true 。
使用SHA3FIPS256函数获取哈希值¶
SHA3-256是SHA-3安全散列算法(遵循FIPS202)系列的一部分,其算法的输出长度为256比特。尽管SHA-3的名称与SHA256相似,但SHA-3系列使用完全不同的算法构建,因此对于相同的输入产生的哈希输出与SHA256不同。您可以使用此在线工具自行验证。计算SHA3-256输出后,在下拉菜单将算法改为SHA256并记下结果输出。
目前Solidity暂不支持SHA3-256,因此需要使用内联汇编的方式调用这一函数。您可使用以下代码调用这个预编译合约。
pragma solidity >=0.4.21;
contract Precompiles {
function sha3fips(bytes memory data) public view returns (bytes32) {
bytes32[1] memory h;
assembly {
if iszero(
staticcall(not(0), 0x400, add(data, 32), mload(data), h, 32)
) {
invalid()
}
}
return h[0];
}
}
使用Remix编译器部署并将MetaMask连接至Moonbase Alpha即可部署合约。调用sha3fips(bytes memory data)
方法返回数据参数的编码字符串。
使用RIPEMD160函数获取哈希值¶
向这一函数输入数据可返回其RIPEMD-160哈希值。测试这一预编译合约,可以使用这个在线工具来计算任何字符串的RIPEMD-160哈希值。在本示例中,我们仍使用Hello World!
。我们将使用相同的代码,但使用另一个函数:ripemd160
函数。请注意,这个函数返回的是bytes20
类型的变量:
pragma solidity ^0.7.0;
contract HashRipmd160{
bytes20 public expectedHash = hex'8476ee4631b9b30ac2754b0ee0c47e161d3f724c';
function calculateHash() internal pure returns (bytes20) {
string memory word = 'Hello World!';
bytes20 hash = ripemd160(bytes (word));
return hash;
}
function checkHash() public view returns(bool) {
return (calculateHash() == expectedHash);
}
}
合约部署后,就可以调用checkHash()
方法进行验证。如果calculateHash()
返回的哈希值与所提供的哈希值一致,即返回true 。
BN128Add¶
BN128Add预编译实现了原生椭圆曲线点添加。它返回一个表示(ax, ay) + (bx, by)
的椭圆曲线点,这样(ax, ay)
和(bx, by)
是曲线BN256上的有效点。
目前Solidity暂不支持BN128Add,因此需要使用内联汇编的方式调用这一函数。您可使用以下代码样本调用这个预编译合约。
pragma solidity >=0.4.21;
contract Precompiles {
function callBn256Add(bytes32 ax, bytes32 ay, bytes32 bx, bytes32 by) public returns (bytes32[2] memory result) {
bytes32[4] memory input;
input[0] = ax;
input[1] = ay;
input[2] = bx;
input[3] = by;
assembly {
let success := call(gas, 0x06, 0, input, 0x80, result, 0x40)
switch success
case 0 {
revert(0,0)
}
}
}
}
使用Remix编译器部署并将MetaMask连接至Moonbase Alpha即可部署合约。调用callBn256Add(bytes32 ax, bytes32 ay, bytes32 bx, bytes32 by)
方法返回操作结果。
BN128Mul¶
BN128Mul预编译实现了原生椭圆曲线的标量乘法。它返回一个椭圆曲线点,表示scalar * (x, y)
,使得(x, y)
是曲线BN256上的有效曲线点。
目前Solidity暂不支持BN128Mul,因此需要使用内联汇编的方式调用这一函数。您可使用以下代码调用这个预编译合约。
pragma solidity >=0.4.21;
contract Precompiles {
function callBn256ScalarMul(bytes32 x, bytes32 y, bytes32 scalar) public returns (bytes32[2] memory result) {
bytes32[3] memory input;
input[0] = x;
input[1] = y;
input[2] = scalar;
assembly {
let success := call(gas, 0x07, 0, input, 0x60, result, 0x40)
switch success
case 0 {
revert(0,0)
}
}
}
}
使用Remix编译器部署并将MetaMask连接至Moonbase Alpha即可部署合约。调用callBn256ScalarMul(bytes32 x, bytes32 y, bytes32 scalar)
方法返回操作结果。
BN128Pairing¶
BN128Pairing预编译通过椭圆曲线配对操作进行zkSNARK验证。更多信息请访问EIP-197。
目前Solidity暂不支持BN128Pairing,因此需要使用内联汇编的方式调用这一函数。您可使用以下代码调用这个预编译合约。
pragma solidity >=0.4.21;
contract Precompiles {
function callBn256Pairing(bytes memory input) public returns (bytes32 result) {
// input is a serialized bytes stream of (a1, b1, a2, b2, ..., ak, bk) from (G_1 x G_2)^k
uint256 len = input.length;
require(len % 192 == 0);
assembly {
let memPtr := mload(0x40)
let success := call(gas(), 0x08, 0, add(input, 0x20), len, memPtr, 0x20)
switch success
case 0 {
revert(0,0)
} default {
result := mload(memPtr)
}
}
}
}
使用Remix编译器部署并将MetaMask连接至Moonbase Alpha即可部署合约。调用function callBn256Pairing(bytes memory input)
方法返回操作结果。
恒等函数¶
这一函数也被称为“数据复制”函数,是复制内存数据性价比较高的方法。
目前Solidity暂不支持恒等函数,因此需要使用内联汇编的方式调用这一函数。您可以使用以下代码(经修改已适应Solidity)调用这个预编译合约。我们可以使用此在线工具来获取任何字符串的字节码,因为它是callDataCopy()
方法的输入值。
pragma solidity ^0.7.0;
contract Identity{
bytes public memoryStored;
function callDatacopy(bytes memory data) public returns (bytes memory) {
bytes memory result = new bytes(data.length);
assembly {
let len := mload(data)
if iszero(call(gas(), 0x04, 0, add(data, 0x20), len, add(result,0x20), len)) {
invalid()
}
}
memoryStored = result;
return result;
}
}
合约部署后,就可以调用callDataCopy()
方法来验证memoryStored
是否与您在函数中所输入的字节码相符。
模幂运算函数¶
我们要介绍的最后预编译合约主要用于计算整数b(基数)乘以e次方(指数)并除以一个正整数m(除数)后的余数。
目前Solidity暂不支持模幂运算函数,因此需要使用内联汇编的方式调用这一函数。以下代码经过了简化,能更好地呈现这一预编译合约的功能。
pragma solidity ^0.7.0;
contract ModularCheck {
uint public checkResult;
// Function to Verify ModExp Result
function verify( uint _base, uint _exp, uint _modulus) public {
checkResult = modExp(_base, _exp, _modulus);
}
function modExp(uint256 _b, uint256 _e, uint256 _m) public returns (uint256 result) {
assembly {
// Free memory pointer
let pointer := mload(0x40)
// Define length of base, exponent and modulus. 0x20 == 32 bytes
mstore(pointer, 0x20)
mstore(add(pointer, 0x20), 0x20)
mstore(add(pointer, 0x40), 0x20)
// Define variables base, exponent and modulus
mstore(add(pointer, 0x60), _b)
mstore(add(pointer, 0x80), _e)
mstore(add(pointer, 0xa0), _m)
// Store the result
let value := mload(0xc0)
// Call the precompiled contract 0x05 = bigModExp
if iszero(call(not(0), 0x05, 0, pointer, 0xc0, value, 0x20)) {
revert(0, 0)
}
result := mload(value)
}
}
}
您也可以在Remix环境中尝试使用这一合约。调用verify()
函数,输入基数、指数和除数,结果将储存在函数的checkResult
变量中。
| Created: March 26, 2021