Skip to main content

Quickstart

anyrand is open-source software. Anyone can deploy their own instance of the anyrand contracts on any EVM-compatible network (provided it supports the necessary precompiles) to produce verifiable randomness.

If you'd rather not have this request/response protocol and the overhead of running keeper infrastructure, read ahead to learn how to manually verify drand signatures and produce verifiable randomness in your contract!

How to fetch randomness

The drand relays provide a simple REST API for fetching the BLS group signatures for each drand round produced by the nodes of the League of Entropy. In this section, we'll learn how to fetch these signatures and other important information from the relays using the v2 API.

Fetching the beacon info

First, we'll need to know important details about the evmnet beacon. Using the v2 API, we make the following request in our terminal:

curl https://api.drand.sh/v2/beacons/evmnet/info

which returns the following response:

{
"public_key": "07e1d1d335df83fa98462005690372c643340060d205306a9aa8106b6bd0b3820557ec32c2ad488e4d4f6008f89a346f18492092ccc0d594610de2732c8b808f0095685ae3a85ba243747b1b2f426049010f6b73a0cf1d389351d5aaaa1047f6297d3a4f9749b33eb2d904c9d9ebf17224150ddd7abd7567a9bec6c74480ee0b",
"period": 3,
"genesis_time": 1727521075,
"genesis_seed": "cd7ad2f0e0cce5d8c288f2dd016ffe7bc8dc88dbb229b3da2b6ad736490dfed6",
"chain_hash": "04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3",
"scheme": "bls-bn254-unchained-on-g1",
"beacon_id": "evmnet"
}

From this response, we now know the public key, the period, and the genesis time, which we'll need for computing rounds and verifying signatures.

Fetching the round signature

Next, we'll fetch a signature for a specific round. We'll need to know the round number for the signature we want to fetch. We can compute this using the period, genesis_time and the current block.timestamp. In most cases, we want to fetch a future round's signature. If we set a deadline timestamp in the future of when we want the randomness to be revealed, we can compute the nearest round number that is after this timestamp like so:

uint256 delta = deadline - genesis_time;
uint64 round = uint64((delta / period) + ((delta % period) > 0 ? 1 : 0));

Of course now, we must wait! But after the deadline has passed, we should be able to fetch the signature for this round from the relay. Using the v2 API, we make the following request in our terminal:

# Replace ${round} with the round number we computed above
curl https://api.drand.sh/v2/beacons/evmnet/rounds/${round}

The response will look something like this:

{
"round": 450761,
"signature": "2a02d63da4c4c56aa035c386cec12b24112890b74049a23953e76f85d9c5a28b184ef1e2e5b2fee9dee5c1e22366338689dc91752fe36f90216fd19169a89147"
}

Your round number and signature will be different, depending on which round you queried!

Getting it onchain

Now that we have the round number and the signature, we can verify the signature using the beacon's public key. The signature is simply a BLS signature (a G1 point on the BN254 curve), which we can verify using the bls-bn254 library.

Installing the BLS library

To install the BLS library to help us verify signatures:

npm install --save @kevincharm/bls-bn254

You'll also probably need the noble-curves libraries:

npm install --save @noble/curves @kevincharm/noble-bn254-drand

Once we have that installed, we can then import it into our Solidity contract:

import {BLS} from "@kevincharm/bls-bn254/contracts/BLS.sol";

Setting up the drand beacon parameters

It is necessary to decompose the public key from its hexadecimal representation into 4 256-bit unsigned integers that represent its G2 point coordinates. We can do this using the noble-curves library we installed previously.

import { bn254 } from "@kevincharm/noble-bn254-drand";

// evmnet beacon parameters, fetched from a previous step
const evmnet = {
public_key:
"07e1d1d335df83fa98462005690372c643340060d205306a9aa8106b6bd0b3820557ec32c2ad488e4d4f6008f89a346f18492092ccc0d594610de2732c8b808f0095685ae3a85ba243747b1b2f426049010f6b73a0cf1d389351d5aaaa1047f6297d3a4f9749b33eb2d904c9d9ebf17224150ddd7abd7567a9bec6c74480ee0b",
period: 3,
genesis_time: 1727521075,
genesis_seed:
"cd7ad2f0e0cce5d8c288f2dd016ffe7bc8dc88dbb229b3da2b6ad736490dfed6",
chain_hash:
"04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3",
scheme: "bls-bn254-unchained-on-g1",
beacon_id: "evmnet",
};

// Deserialise the public key from hex to a normalised G2 point
const pkAffine = bn254.G2.ProjectivePoint.fromHex(public_key).toAffine();

// Now format as input for the contract constructor
const publicKey = [pkAffine.x.c0, pkAffine.x.c1, pkAffine.y.c0, pkAffine.y.c1];
const genesisTimestamp = evmnet.genesis_time;
const period = evmnet.period;

Once we have the formatted public key, genesis timestamp and period, we can feed these into the constructor of our contract:

uint256[4] public publicKey;
uint256 public genesisTimestamp;
uint256 public period;

constructor(
uint256[4] memory publicKey_,
uint256 genesisTimestamp_,
uint256 period_
) {
if (!BLS.isValidPublicKey(publicKey_)) {
revert InvalidPublicKey(publicKey_);
}
publicKey = publicKey_;
genesisTimestamp = genesisTimestamp_;
period = period_;
}

Oh, and don't forget the domain separation tag, we'll need it for hashing the round later.

/// @notice Domain separation tag
bytes public constant DST = bytes("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_");

Hashing the round

The round number is interpreted as a 64-bit unsigned integer. First we need to hash it (only the 8 bytes!) using keccak256 to get a 256-bit number, then we can feed the digest into BLS#hashToPoint to get the uniform G1 point; the message that the round's BLS signature was computed over.

// Encode round for hash-to-point
bytes memory hashedRoundBytes = new bytes(32);
assembly {
mstore(0x00, round)
let hashedRound := keccak256(0x18, 0x08) // hash the last 8 bytes (uint64) of `round`
mstore(add(0x20, hashedRoundBytes), hashedRound)
}
uint256[2] memory message = BLS.hashToPoint(DST, hashedRoundBytes);

Verifying the signature

Now we check whether the signature is indeed a valid signature for the message we computed in the previous step, using the public key that was deserialised from the relay.

// NB: Always check that the signature is a valid signature (a valid G1 point on the curve)!
bool isValidSignature = BLS.isValidSignature(signature);
if (!isValidSignature) {
revert InvalidSignature(pubKey, message, signature);
}

// Verify the signature over the message using the public key
(bool pairingSuccess, bool callSuccess) = BLS.verifySingle(
signature,
pubKey,
message
);
if (!pairingSuccess) {
revert InvalidSignature(pubKey, message, signature);
}

Producing verifiable randomness

Now the part you've been waiting for! While a signature gives us some entropy, we need to feed that into something that approximates a random oracle to output uniform randomness. So finally, we feed the signature as inputs into keccak256.

bytes32 randomness = keccak256(abi.encode(signature));

The digest is then the randomness. You can optionally mix in some salts such as the requester's address, your contract address and chain ID to make your randomness unique to other contracts requesting from the same round. Congratulations, you've just produced verifiable randomness!