PlatON网络对链上随机数的支持 | 技术云图

PlatON网络对链上随机数的支持 | 技术云图

PlatON网络已升级至1.2.0版本,其中包含了2个重要更新,同时支持100和210425两个ChainID与支持智能合约获得随机数,本文将详解PlatON网络对链上随机数的支持。

背景

以NFT、Loot drops游戏等应用为代表的DApp极大地促进了区块链生态的繁荣,链上随机数可以为DApp提供防篡改的链上随机性,诸如NFT铸造和归属、draws、(PvP) Battles等都需要使用链上随机来产生公平的结果,为此,在PlatON网络上急需支持一套安全的、和应用具有良好兼容性的链上随机数解决方案。

概述

在公有链上通过共识算法独立产生不可预知的随机数是困难的,PlatON的共识算法Giskard中使用了VRF算法做验证人选取,其链上Nonce(VRF和证明)天然的具备安全、可验证以及随机性(不可预知)的特点,因此可以直接在PlatON中通过使用区块Header中的Nonce来满足链上(合约层)获取随机性的要求。

实现

由于solidity并未提供获取Nonce的指令,EVM类似的提案EIP-4399也尚未进入实施阶段,为了保持和EVM良好的兼容性,PlatON上讲采用 PrecompiledContract 的方式在合约层支持用户获取随机数。

随机源

PlatON的链上随机数来源于区块header中的Nonce,该字段由父区块的Nonce做为种子,由当前区块的提议人私钥签名产生的随机数,天然具有可验证、随机性。使用时,取该字段的第[1,33]字节为可验证的随机数的随机源VRF。

PlatON网络中的所有验证人节点都将对区块Header中的Nonce字段做验证,如果该字段非法,则当前区块不能获取验证人的签名,无法被确认,因此该字段无需在合约层重复验证。

用户接口

  • 预编译合约

系统通过PrecompiledContract的VrfInnerContract获取区块Header中的Nonce,合约地址为:

lat1xqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpe9fgva (0x3000000000000000000000000000000000000001) 

调用时,用户需要传入产生随机数的个数, 该PrecompiledContract返回随机数数组。

  • 调用

智能合约中可以使用delegatecall请求随机数,如:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * 调用PlatON内置合约生成VRF随机数
 */
contract VRF {

  error InvalidRandomWords(uint32 numWords, uint256 returnValueLength);

  // VrfInnerContract 内置合约地址
  address vrfInnerContractAddr = 0x3000000000000000000000000000000000000001;

  // 32个字节表示uint256
  uint32 base = 32;

  /**
   * 调用 VrfInnerContract 内置合约生成VRF随机数
   * @param numWords 随机数的个数
   */
  function requestRandomWords(uint32 numWords) internal returns (uint256[] memory) {
    bytes memory data = abi.encode(numWords);
    bytes memory returnValue = assemblyCall(data, vrfInnerContractAddr);

    if (numWords * base != returnValue.length) {
        revert InvalidRandomWords(
            numWords,
            returnValue.length
        );
    }

    uint256[] memory randomWords = new uint256[](numWords);
    for(uint i = 0; i < numWords; i++) {
        uint start = i * base;
        randomWords[i] = sliceUint(returnValue, start);
    }

    return randomWords;
  }

  /**
   * delegatecall 合约
   * @param data 合约input data
   * @param addr 合约地址
   */
    function assemblyCall(bytes memory data, address addr) internal returns (bytes memory) {
        uint256 len = data.length;
        uint retsize;
        bytes memory resval;
        assembly {
            let result := delegatecall(gas(), addr, add(data, 0x20), len, 0, 0)
            retsize := returndatasize()
        }
        resval = new bytes(retsize);
        assembly {
            returndatacopy(add(resval, 0x20), 0, returndatasize())
        }
        return resval;
    }

    function sliceUint(bytes memory bs, uint start) internal pure returns (uint256) {
        require(bs.length >= start + 32, "slicing out of range");
        uint256 x;
        assembly {
            x := mload(add(bs, add(0x20, start)))
        }
        return x;
    }
}

注意:

1. 用户请求单个随机数

内置合约通过计算随机源VRF与当前用户交易hash的异或直接返回结果

for i := 0; i < txHash.Length; i++ {
 randomWords[i] = currentNonces[i] ^ txhash[i]
}

2. 用户请求随机数个数为n( n>1)

内置合约VrfInnerContract通过计算随机源VRF与当前用户交易hash的异或,再与当前区块的前n个区块的VRF分别异或,顺序为从parent区块到currentBlockNumber-n+1块,然后将所有计算结果依次拷贝到返回结果的数组中

for i := 0; i < txHash.Length; i++ {
 randomWords[i] = currentNonces[i] ^ txhash[i]
}

vrf := handler.GetVrfHandlerInstance()
nonceInVrf, err := vrf.Load(v.Evm.ParentHash)

for i := 1; i < int(randomWordsNum); i++ {
 // 优先从VrfHandler中获取nonce, 当获取不到则从区块中拿
 if i+1 > len(nonceInVrf) {
  preNonce = vrf2.ProofToHash(v.Evm.GetNonce(currentBlockNum - uint64(i) - 1))
 } else {
  preNonce = nonceInVrf[len(nonceInVrf)-i-1]
 }
 start := i * common.HashLength
 for j := 0; j < common.HashLength; j++ {
  randomNumbers[j+start] = randomNumbers[j] ^ preNonce[j]
 }
}

本文转载自https://mp.weixin.qq.com/s/F1aJZ6oHwCS91nFkD2GTgw

(0)
上一篇 6 5 月, 2022 10:14
下一篇 7 5 月, 2022 10:31

相关推荐

发表回复

登录后才能评论