The randomness
The winner is chosen from a signature that does not exist when the raffle closes, published on a schedule by a network that has never heard of this product.
drand, and what it is
drand is a public randomness beacon. A group of independent operators jointly signs a counter — round 1, round 2, and so on — every 3 seconds, and publishes the signature. No single operator can produce a valid signature alone, and the signature for a round is determined by the round number: there is exactly one, and it does not exist until the network produces it.
This vault uses the chain drand calls quicknet, on BLS12-381:
chain hash 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971
period 3 seconds
You can read the beacon yourself at https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest — it is drand's API and nothing of ours sits in front of it.
The target round is fixed when the raffle closes
When a raffle closes — because it sold out, or because its deadline passed — the contract computes a target round from the close time plus a margin, and records it. That happens before the round has been published.
round = roundAt(closeTimestamp + margin)
closeTimestamp = the sold-out transaction's block time, or the deadline
The margin on this resolver is 1 minute.
Two things follow, and they are the whole design:
- The round is not chosen at creation. A round picked when the raffle opened would be a round whose value could become public while tickets were still selling.
- The margin exists because "at close" is not a precise instant. Block time drifts. Without it, somebody buying the last ticket as the beacon published would be entering a raffle whose seed already existed. It is fixed when the resolver is deployed and cannot be changed afterwards.
No entry lands after the close, by any path. That is the invariant the rest of this rests on: if an entry could arrive after the round is determined, it would not be a raffle.
Anyone submits it, and the contract checks it
Once the round publishes, anyone may hand the signature to the resolver at 0x72fa0ff3B85a68E52331033e8732089fc95F9fb7. There is no privileged submitter and no queue. The contract then verifies the signature against drand's own public key using the BLS pairing precompiles on this chain, and only a valid signature for the recorded round is accepted.
The seed is keccak256 of that signature. The winner is found by binary search over cumulative ticket ranges, so the cost of settling does not grow with the number of entrants.
A small tip — $0.05 — is paid to whoever submits, out of the protocol's share rather than out of anybody's prize. Somebody has to pay for the transaction that pushes the raffle forward, and it should not be the winner.
What a submitter can and cannot influence
Cannot: the signature. It is determined by the round number and produced by drand, and the resolver accepts no other. There is exactly one valid signature for the target round, so there is nothing for a submitter to choose between.
Cannot: the round. It was recorded at close, before the signature existed.
Cannot: the encoding. The same signature has a short and a long form whose hashes differ, and accepting both would hand the submitter a choice between two seeds. The resolver accepts one form only.
Can: when the raffle settles, by submitting sooner or later — and whether it settles at all, by not submitting. That is why anyone can submit and why the tip exists: a raffle that nobody pushes forward is the failure this design has to answer for, and the answer is refund mode, which opens 7 days after the close.
One thing that is not the submitter's, and is worth naming. The close timestamp is a block timestamp, so a party who can move block timestamps could move the target round. The margin is set to exceed the chain's drift for that reason. The mover there would be a block proposer, not a submitter.
Check it yourself
For any drawn raffle, its page shows the target round, the seed and who submitted it. To check the draw:
- Take the round number from the raffle page.
- Fetch that round from drand:
https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/<round>. - The
signaturethere is the one the contract verified. The seed is itskeccak256. - The winning ticket is the seed modulo the number of tickets sold, and the raffle's entries say which address holds it.
Nothing in that chain of steps passes through us.
What this rests on
The contract does not check the submitted point's subgroup itself. It relies on this chain's BLS precompiles rejecting a malformed one, which they were measured doing before the resolver was deployed. That is a property of the chain rather than of our code, it is checked by a test that runs against the real precompiles, and it is the assumption the randomness design depends on most.
The resolver verifies a known drand round in its own constructor, so on a chain without those precompiles it cannot be deployed at all — rather than deploying and failing on the first settlement.