On-chain random number support on PlatON networks

PlatON Network
PlatON Network
Published in
4 min readMay 12, 2022

The PlatON network has been upgraded to version 1.2.0, which includes two important updates, supporting both 100 and 210425 ChainIDs and supporting smart contracts to obtain random numbers.

Background

DApps such as NFT, Loot drops games and other applications have greatly contributed to the prosperity of the blockchain ecosystem. On-chain random numbers can provide tamper-proof on-chain randomness for DApps, such as NFT casting and attribution, draws, (PvP) Battles, etc. all need to use on-chain random to produce fair results. support a secure on-chain random number solution with good application compatibility.

Overview

It is difficult to independently generate unpredictable random numbers on a public chain through a consensus algorithm; PlatON’s consensus algorithm, Giskard, uses the VRF algorithm for verifier selection, and its on-chain Nonce (VRF and proof) is naturally secure, verifiable and random (unpredictable). Therefore, the requirement for on-chain (contract layer) randomness can be satisfied directly in PlatON by using the Nonce in the block header.

Implementation

As solidity does not provide a command to obtain Nonce and the similar proposal EIP-4399 for EVM is not yet in the implementation phase, in order to maintain good compatibility with EVM, PlatON uses the PrecompiledContract approach to support the user to obtain random numbers at the contract level.

Random source

PlatON’s on-chain random numbers come from the Nonce in the block header. This field is seeded by the Nonce of the parent block and is a random number generated by the private key signature of the current block’s proposer, which is naturally verifiable and random. When used, the [1,33] byte of this field is taken to be the random source VRF for the verifiable random number.

All verifier nodes in the PlatON network will do verification of the Nonce field in the block Header. If the field is illegal, the current block cannot obtain the verifier’s signature and cannot be validated, so the field does not need to be repeatedly verified at the contract level.

User Interface

PrecompiledContract

The system obtains the Nonce in the block Header through the VrfInnerContract of the PrecompiledContract, with the contract address:lat1xqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpe9fgva (0x3000000000000000000000000000000000000001)

When called, the user needs to pass in the number of random numbers to be generated, and the PrecompiledContract returns an array of random numbers.

Calling

A random number can be requested in a smart contract using delegatecall, e.g.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* Calling PlatON's built-in contract to generate VRF random numbers
*/
contract VRF {
error InvalidRandomWords(uint32 numWords, uint256 returnValueLength);// VrfInnerContract Built-in contract address
address vrfInnerContractAddr = 0x3000000000000000000000000000000000000001;
// 32 bytes for uint256
uint32 base = 32;
/**
* Call VrfInnerContract built-in contract to generate VRF random numbers
* @param numWords Number of random numbers
*/
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 contract
* @param data contract input data
* @param addr contract address
*/
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;
}
}

Caution.

1. User requests a single random number

The built-in contract returns the result directly by computing the dissimilarity between the random source VRF and the current user transaction hash

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

2. users request a random number of n (n > 1)

The built-in contract VrfInnerContract computes the random source VRF as an anomaly with the current user transaction hash, then anomalies it with the VRFs of the first n blocks of the current block, in the order from parent block to currentBlockNumber-n+1 blocks, and copies all the computed results in turn to the array of returned results.

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++ {
// Prioritize fetching nonce from VrfHandler, when not fetching then fetch from block
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]
}
}

--

--

PlatON Network
PlatON Network

PlatON — An Infrastructure for Privacy-Preserving Computation and Distributed Economies.