The proposed architecture is split into an on-chain and an off-chain component:

  1. The on-chain component is almost unchanged from its current form, except that there is no requests in getPseudoRandom; rather, there is a new function supplyRandom that accepts (uint256[2] calldata pk, uint256[2] calldata gamma, uint256 c, uint256 s, uint256 alpha). This function is whitelisted to a number of addresses that will correspond to off-chain software run on our servers. The contract will be endowed with methods for whitelist manipulation. In addition, a pointer to a current position in the list of random numbers will be maintained. Upon reception of the proof from a whitelisted address the contract must check that the most recent submission is no less than an interval in the past, checks the proof, generates the randomness from it, and writes the obtained number at the pointer. The pointer is incremented modulo the list length (so, in essence, we have a cyclically updated list, one number at a time).

<aside> ⚠️ The order of submission is governed off-chain. We can afford it because everything is in-house.

</aside>

  1. The off-chain component. This script connects via WebSockets to an RPC and monitors it. It has two conditions for triggering randomness generation and sending:

    1. The ID of the associated address, read at initialization, equals block.number/blocksInEpochContract.whitelistedkeepers.length. This makes sure that everyone has a window within which to submit.
    2. The last refreshment of randomness was too long ago (the threshold is parameterized). This will engender some gas losses and can be disabled if we desire. An alternative to this could be (id == block.number/blocksInEpochContract.whitelistedkeepers.length) or ((id - 1 == block.number/blocksInEpochContract.whitelistedkeepers.length ) and (lastRefreshmentTooStale))

    Upon sending, a proof is generated and sent. The randomness is produced on-chain therefrom.

Algorithmic details

The proposed algorithm is described in Making NSEC5 Practical for DNSSEC (iacr.org) and RFC 9381 - Verifiable Random Functions (VRFs) (ietf.org) (the latter developed on the basis of the former).

PUBLIC ON-CHAIN INSTANTIATION PARAMETERS

  1. q - prime number
  2. g - generator begetting a finite cyclic group G of order q. Goldberg recommends P-256 and edwards25519 (sect. 5.5. of RFC 9381); I recommend we go with secp256k1, as we can do ECMUL cheaply with it (see: https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384. If we use this hack, we should reduce the value u to its 20 lowest bytes instead)
  3. E - supergroup of G, membership in which is checked easily, and the cofactor of G in E is indivisible by q. We have no need of this parameter
  4. H1: Z → G - {1}, H2: E → Z, H3: Z → $Z_l$, where $Z_l$ is understood to be populated by *l-*bit integers, are hash functions. We take H2 as keccak256, and H3 as l-truncated keccak256 (Goldberg recommends sha256, but it’s more expensive)

OFFCHAIN SIDE

  1. x ← UNIFORM(1, q-1)
  2. PK ← g**x
  3. alpha ← RANDOM (any distribution here)
  4. h ← H1(alpha)
  5. gamma ← h**x
  6. k ← UNIFORM(0, q-1)
  7. c ← H3(g, h, gx, hx, gk, hk)
  8. s ← k-cx mod q