0

Would this be a non-malleable way to generate numbers for my particular use case?

Let me explain:
I allow people to register for a lottery-type event.
If a winning seed belongs to their ticket set, they win selection to participate in a future activity.

OBS: I'm aware that multiple winning seeds can be associated with the same registrant but this is out of scope for this question.

Here's an example.

    pragma solidity ^0.5.0;

    contract ExampleRNG {

        //@seedTrigger: number which triggers generation of a winning seed.
        uint public seedTrigger; 

        //@registrantCounter: counter which resets everytime a winning seed is generated.
        uint public registrantCounter;

        //@winningSeeds: array which stores the generated winning seeds. 
        //let's assume that this is a low number to avoid infinite loop discussions.
        uint[] internal winningSeeds;

        //@winningThreshold: number which if reached, restricts further seeding/registration activity. 
        uint public winningThreshold; 

        //@latestSeed: the latest seed generated from a new registration.
        bytes32 public latestSeed;

        //@stakes: total amount of stakes from registrations.
        uint public stakes;

        //@Tickets: struct to keep track of each registrant's ticket set (interval).
        struct Tickets {
            uint lower;
            uint upper;
        }

        //@ticketsets: storage of each registrant's ticket sets.
        mapping(address => Tickets) public ticketsets;


        constructor(uint _seedTrigger, uint _winningThreshold) public {
            seedTrigger = _seedTrigger;
            winningThreshold = _winningThreshold;
        }

        //@dev register to a lottery-type event.
        //@param stake: think of this as a weighted interest/probability for being selected as a winning seed. 
        function register(uint stake) external {
            require(winningSeeds.length <= winningThreshold, "[!] the registration activity has been closed");

            ticketsets[msg.sender].lower = stakes;
            ticketsets[msg.sender].upper = stakes + stake;

            latestSeed = keccak256(abi.encodePacked(latestSeed, stake));

            stakes = stakes + stake;

            registrantCounter++;

            if (registrantCounter == seedTrigger) {
                uint winningSeed = uint(latestSeed) % stakes;
                winningSeeds.push(winningSeed); 
                registrantCounter = 0;
            }
        }

        function revealWinningSeeds() external view returns(uint[] memory) {
            return(winningSeeds);
        }
    }
0

Basically any way of generating randomness within a contract is prone to abuse and is unsafe. Deterministic contracts are really bad at generating random numbers.

In your case it is easy to check what is the latest latestSeed and then calculate the right stake to trigger winning to make yourself win (or if not possible currently then wait for the next latestSeed).

To make real random numbers you need external help. Oracles is one option but that includes a trusted third party. Another option is something like Randao (https://github.com/randao/randao)

  • Hi Lauri, how would you calculate the 'right stake' ? there is no right stake, the latest seed is modified for every registrant by a self-selective stake and the final winning seed is calculated using the total sum of stakes which is unknown till seedTrigger is triggered. – NowsyMe Aug 31 at 9:30
  • ok I actually have no idea how the system works as there's only a part of the code and I assumed winningSeed means winning in some way. And I thought the user chooses the stake argument. So, I have no idea how the system works and it seems to be too complex to be analyzed in a single question. – Lauri Peltonen Aug 31 at 9:44
  • that's the entirety of the code and you're right, winningSeed means winning in some way. Also, the user chooses the stake argument but winners are chosen based on amount of registrants and not a stake value. The stake just increases the probability of being chosen. The only way I see someone could make oneself winner is to stake an amount which brings them the closest possible to 100% probability. – NowsyMe Aug 31 at 10:44
  • 1
    Yes, and that is gaming the system. And as I said, another way is to wait for a "good" latestSeed. It's all about increasing your odds. With a truly random seed the odds can't be increased. – Lauri Peltonen Aug 31 at 15:01
1

I can use the following function:

function foo(uint n) public {
    require(yourContract.winningThreshold() == yourContract.registrarCounter() + 1);
    uint CurrentStake = yourContract.stake();
    yourContract.register(n);
    uint[] memory seeds = yourContract.revealWinningSeeds();
    require(seeds[seeds.length - 1] > currentStake);
}

Every non-reverted tx will be the winning one. To make the process faster I can fill the users array by sending the minimal stake.

  • what users array can you fill? there are no users array. There's only an array for winning seeds which computes and stores a seed for every 4th registration. What is stake()? and registrarcounter() I have no such functions. Would be nice if you could elaborate a little, thanks! – NowsyMe Aug 30 at 21:11
  • Oh, I meant increasing registrantCounter variable, not users array. stake() and registrarCounter() functions are public getters automatically generated by the compiler since your contract includes public variables. These getters simply return values of the state variables with the same names. – Igor Sobolev Aug 31 at 22:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.