back to list
2025-08-15
Many fantasy football platforms have clear and transparent features for commissioners to randomize the draft order. Usually the commissioner can schedule a time to have the order randomly chosen, or it can be done ad hoc at a time of their choosing. Since some leagues however might have custom rules, platforms also allow commissioners to manually set a non-random arbitrarily chosen draft order. In certain circumstances the draft order can be very important to members of the league. If your league picks a random draft order, then naturally raises the question: how you know the draft order was chosen truly randomly? We will assume any order chosen by the platform itself is sufficiently random, but how do you know the commissioner didn't roll it a couple extra times until they got the spot they wanted? A good fantasy platform should be very transparent about the decision and should broadcast to the other members that the draft order has been updated. But this isn't universally the case. Or alternatively, if for whatever reason you set a random order manually (generated outside of the fantasy platform), how do you know the order is random?
This blog is describing a simple commitment scheme that allows for a league of players to ensure the order was set fairly. The idea is really a simple variation on "Coin Flipping", but please email me if you notice any holes or have any suggestions.
The goal is to design a simple system that allows a random draft order to be chosen for a league of players, while each can be confident it was chosen at random, and not manipulated by any other member of the league, including the commissioner.
To simplify things, we will say we want to decide on some random number, not necessarily an order.
There are many practical ways we can derive a random order from a simple random integer.
One for instance is using the number to seed a previously discussed generated and using it to
shuffle
the teams in the league, see numpy.random.Generator.shuffle. This isn't very accessible though, so other more practical methods will be discussed towards the end.
First you need to decide on and assemble a few things:
Some of these steps might sound overly specific but they are all important and are there for good reason.
The commissioner should announce that the order generation is starting. They should the initial order that will be used. It doesn't matter how they come up with this order, but it it needs to be agreed upon. Ask each member of the league to make up some secret phrase that they would be willing to share later. Have them write it down somewhere so they don't forget.
Next have them find the sha256 hash of their secret. They can do these by using the previously agreed upon website. Once they have the hash of their secret, they should share it in the league chat. It is very important that they only share the hash (the random string of letters and numbers) not the entire secret.
Once everyone has shared their hash, the commissioner should then ask everyone to share their secret. Each member of the league should then go and ensure that the hash of the secret they shared matches the hash that they reported during the "Sharing" phase. If any of the hashes do not match, this process should be restarted. A mismatch hash indicates either a simple mistake or an intention to rig the draft order.
Assuming each of the hashes match what each member initially reported, you can then move on to calculating the final random number. Compile all of the secrets that the members gave, not the hashes. Next, put the secrets in lexicographical (alphabetical) order. Finally, append all of the secrets together in this order. Do not put any spaces or other symbols between them. You can then place this one long string into the same sha256 calculator. The final sha256 hash is your piece of randomness. This is effectively a 256 bit random number.
You then need to take this large random number and produce an order. One straight forward way to do this would be by using some Python code to shuffle your initial order randomly and seeding the random number generator using the number you calculated in the last step.
This could look something like this:
import numpy as np
teams = np.array(["t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"])
rng = np.random.default_rng(seed=0xae0e657769d5dcc53ef588405ad76db11b36da5995b05c5d844efbf2a952df0e)
rng.shuffle(teams)
print(teams)
The most important thing is that you decide on the procedure for deriving the order from the random number before hand.
After Step 2, everything should be verifiable by each member in the league. Meaning everyone should be able to easily replicate and verify the resulting draft order. If there are even small differences in the way the details, like combining everyone's secrets differently, or using different sha256 calculator websites, then the resulting orders may be different. The key thing that should impact the final draft order is everyone's secrets.
Let's work a simple example of what this might look like in tiny 4 team Sleeper league.
The 4 teams are:
Sara picks the following initial starting order:
Sara then says they'll use the following sha256 calculator website.
The process proceeds as follows:
8dcbd7a90e4970405efdfe5b4f598461c3b29a8dade6eb70893f3edf90092365
7c95d7bf76008830030d2383d60f88ba97e69e50ea8e0655cd69ad54c7d7cafa
a2d0116ab97dd735c6108567a8c008d83829705e471cf57fab1f414eb0f1cfb0
125a6774223cea3b09bbba0b6254e746926553ce07f6de256e8be378e85e5fff
Aditi rules 5674
super-secret-phrase-4321
54345317888653
secret001121
54345317888653
Aditi rules 5674
secret001121
super-secret-phrase-4321
54345317888653Aditi rules 5674secret001121super-secret-phrase-4321
54e66f9507de74eeed0bd05921a887687fef8b89c82d84991d20223a248bce04
import numpy as np
# This is the initial order decided before starting the process
teams = np.array(["Todd's Squad", "Aditi's Team", "Sara's Team", "Larry's Team"])
# This is the hash we found in the previous step
our_random = 0x54e66f9507de74eeed0bd05921a887687fef8b89c82d84991d20223a248bce04
rng = np.random.default_rng(seed=our_random)
rng.shuffle(teams)
print(teams)
# ["Aditi's Team", "Sara's Team", "Todd's Squad", "Larry's Team"]
What we'd really like to be able to do, is just have everyone pitch in their secrets, hash them into one number, and then run with that as our randomness used to generate the order. However, the issue is that if anyone in the league as the opportunity to go last, which is obviously inevitable, they have the opportunity to "choose" the draft order by picking a secret that generates a hash that shuffles the order in a desirable way.
The key that makes this approach work is the hash function. These functions take some input data (our secrets) and produce some random looking output. In addition, these functions are not reversible. So if you have the hash, there is absolutely no way to know the original input. However, if the original input is later revealed it's trivially easy to get the same hash. The hash that each player provides are commitments. Meaning that each player is "committing" to their secret without having to reveal to anyone what that secret is. If a player later tries to lie by saying their secret is something other than what they originally committed to, it'll be easy to confirm they lied by hashing their secret and seeing it doesn't match their commitment.
So by making every player commit to their secret before revealing them, we can be sure that no one, including the last player to provide their secret, can manipulate the draft order.
This is all great, but realistically your league of 10 to 12 aren't going to go through all of this effort. If you use a more modern platform like Sleeper that announces anytime the draft order changes or is re-rolled, that is really just as fair as this. If you have those options available just use those.