Randomness
Randomization is an important feature in blockchain. The application of randomization results, such as generated random numbers, can be achieved similarly to, e.g., the election of block nodes, while trusted random numbers can provide support for numerous DApps, such as lotto, games, etc.
In many other chains, the implementation of trusted random number generation requires the use of off-chain random number generation services, such as Ethereum, which requires off-chain beacons such as Chainlink or drand to provide external randomness.
The disadvantages of relying on external beacons are that the randomness is too expensive or too slow to be generated for DApps that require higher speed random numbers; in addition, the external randomness must be sent to the contract through transactions, which complicates DApp development and user interaction.
Reliable Randomness
If a sequence is to be identified as random, it has to possess the following qualities:
Unpredictable β The result must be unknowable ahead of time.
Unbiased β Each outcome must be equally possible.
Provable β The result must be independently verifiable.
Tamper-proof β The process of generating randomness must be resistant to manipulation by any entity.
Non-reproducible β The process of generating randomness cannot be reproduced unless the original sequence is preserved.
Weighted Threshold VUF Essentials
Here we discuss threshold VUF with an idealized key generation algorithm. The advantages of this approach, rather than immediately considering VUF keying using a DKG, is that it lets us focus on the VUF security and avoid any nuances due to the key generation phase.
An -threshold VUF scheme for a finite message space is a tuple of polynomial time computable algorithms with the following properties:
Setup.
(public parameter) consists of a generator and a random generator . Let be the weights of the participants, be the total weights, and be the threshold.
KeyGen, , .
The key generation algorithm takes the public parameters then outputs:
a secret key , where is a random number in group
a vector of secret signing keys , using .
is derived from ShamirShare()
a vector of verification keys , where
a vector of public keys equal to verification keys, i.e., .
The public key size of a party in our weighted VUF is proportional to its weight, i.e., the public key of party contains group elements.
ShareSign.
The partial signing algorithm is a possibly randomized algorithm that takes as input a message and a secret key share . It generates a signature share .
ShareVerify.
The signature share verification function is a deterministic algorithm that takes as input a message , a public key share , and a signature share . It outputs 1 (accept) or 0 (reject).
The function asserts
.
The signature share combining algorithm takes as input the public key , a vector of public key shares , a message , and a set of signature shares (with corresponding indices). It outputs either a signature or β₯.
Derive.
The output derivation algorithm is a deterministic algorithm that takes as input a public key , a message , and a signature , and outputs the VUF output and the proof .
Verify.
The signature verification algorithm is a deterministic algorithm that takes as input a public key , a message , and a VUF output and proof .
verify
run
Derive
of Step 6:check if , and outputs 1 (accept) or 0 (reject).
Unpredictable depends on below:
outcomes aggregated signature, which is the income of VUF output. The procedure takes place in every block's 1st transaction (Block Metadata transaction), which means not any User or Validator could predict randomness in
User Transaction
of the current block.
Provable depends on below:
ShareVerify = 1] = 1$$
Verify(m, Aggregate
Tamper-proof depends on below:
Unbiased depends on below:
VUF output is unbiased if
derivation algorithm is unbiased
input is unbiased, e.g., use hash56(m) as input in the derivation function.
PVSS Process
Endless
PVSS procedure, described as below:
Public Parameter Setup
define groups and , generator , in group and in group ; as numbers of validators, threshold
denote pp (public parameter) = , the following functions will take pp as default input parameter.
PVSS
the key generation protocol generates decryption key , and the corresponding encryption key
validator_{i}, indexed by privately keeps , and publishes
denote
PVSS
leader node takes (secret) as input and generates secret shares
leader node encrypts share with encryption key , outputs encrypted and proof
leader node generates commitment for secret shares
leader node commits transaction
trx
, taking () as PVSStranscript
commit transaction actually is ,
Step. 5
will aggregate these separate into one trx
PVSS Verify
assert
assert
check if is a commitment to valid shares of some secret and the values consist of encryptions of valid shares of the same secret
PVSS Agg
public function Aggregate that anyone can use to aggregate two PVSS transcripts into a single transcript.
repeat to call Agg() to aggregate all into one trx, to save nodes broadcast effort, also save space.
PVSS DecShare
take as input aggregated PVSS transcript trx, index , i-th decryption key , outputs
PVSS Recon
each validator node decrypts with owned to get its share , and publishes with corresponding NIZK proof
anyone could verify with proof , and if shares collected combined weight greater than threshold , the original secret could be recovered.
The Endless chain has built-in provision for trusted random number generation, which can better enhance the security of services based on this type of service. At the implementation level, Endless utilizes the Weighted Publicly Verifiable Secret Sharing (wPVSS) algorithm, which can be run efficiently by each verifying node and which is aggregatable to help reduce communication overhead. Meanwhile, there is a communication-efficient aggregatable weighted distributed key generation (wDKG) generation protocol on Endless. Finally, Endless has a novel Weighted Verifiable Random Function (wVRF), which the verifying node evaluates each round, with a constant amount of communication per verifying node, rather than a linear relationship with the amount pledged by the verifier.
API Usage
randomness::u8_integer()
returns a u8 random number, evenly samples an 8-bit from Endless Randomnessrandomness::u16_integer()
...
randomness::u8_range(min_incl: u8, max_excl: u8)
returns a u8 random number with specified minimal and maximum bound...
randomness::u256_range(min_incl: u256, max_excl: u256)
By repeatedly calling these functions, multiple objects can be safely sampled to generate multiple "unbiased" random numbers.
Demonstration
Endless Roll
Here we build a lottery system and pick a random winner from n
participants. In the centralized world with a trusted server, the roll algorithm is the backend simply calls a random integer sampling function (random.randint(0, n-1)
in Python, or Math.floor(Math.random() * n)
in JS).
In Endless chain, we build a lottery move module:
let winner_idx = endless_framework::randomness::u64_range(0, n);
is the randomness API call that returns a u64 integer in range [0, n) uniformly at random.#[randomness]
enables the move compiler to check the outermost of the call stack ofdecide_winner
is private.
Functions using randomness should be defined with private
, to avoid Test and Abort
attacks. decide_winner
is an entry
function, instead of public entry
.
Security Considerations
Test and Abort
Attack
Test and Abort
AttackIf decide_winner()
were accidentally marked public, malicious players can deploy their own contract to:
call
decide_winner()
;read the lottery result (assuming the lottery module has some getter functions for the result);
abort if the result is not in their favor. By repeatedly calling their own contract until a txn succeeds, malicious users can bias the uniform distribution of the winner (dApp developerβs initial design).
Under Gas
Attack
Under Gas
AttackUsers should also put prevention of Undergassed Attack
into security considerations when writing business logic.
Imagine such a dApp. It defines a private entry function for a user to:
toss a coin (gas cost: 9), then
get a reward (gas cost: 10) if coin=1, or do some cleanup (gas cost: 100) otherwise.
A malicious user can control its account balance, so it covers at most 108 gas units (or set transaction parameter max_gas=108), and the cleanup branch (total gas cost: 110) will always abort with an out-of-gas error. The user then repeatedly calls the entry function until it gets the reward.
Here are some ideas of how to prevent undergassing attacks generally:
Make your entry function gas independent from the randomness outcome. The simplest example is to not βactβ on the randomness outcome, i.e., read it and store it for later. Note that calling any other functions can have variable gas costs. For example, when calling randomness to decide which player should win, and then depositing the winnings to the winner might seem like a fixed gas cost. But,
0x1::coin::transfer
/0x1::fungible_asset::transfer
can have a variable cost based on the userβs on-chain state.If your dApp involves a trusted admin/admin group, only allow the trusted to execute randomness transactions (i.e., require an admin signer).
Make the path that is most beneficial have the highest gas (as the attacker can only abort paths with gas above a threshold he chooses). NOTE: that this can be tricky to get right, and the gas schedule can change, and is even harder to get right when there are more than 2 possible outcomes.
Random, but not a secret
Please keep in mind that random returned from calling the randomness API is randomness, but not a secret. The original seed in each block lies in 0x1::randomness::PerBlockRandomness
which is public on the Endless chain.
Users should not try to do the following:
Use the randomness API to generate an asymmetric key pair, discard the private key, then think the public key is safe.
Use the randomness API to shuffle some opened cards, veil them, and think no one knows the permutation.
Last updated