Skip to content

Instantly share code, notes, and snippets.

@0xAnonymous
Last active September 17, 2021 01:47
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xAnonymous/0d1238bed7ce4bf00b4727001e0a22cb to your computer and use it in GitHub Desktop.
Save 0xAnonymous/0d1238bed7ce4bf00b4727001e0a22cb to your computer and use it in GitHub Desktop.

Anonymous: a proof-of-unique-human system

Anonymous is a coordination game for global proof-of-unique-human, through monthly pseudonym events that last 15 minutes, where every single person on Earth is randomly paired together with another person, 1-on-1, to verify that the other is a human being, in a pseudo-anonymous context. The proof-of-unique-human is that you are with the same person for the whole event. The proof-of-unique-human is untraceable from month to month, much like cash. True anonymity.

When you register for Anonymous, you use register(). You need a “registrationToken” that you got if you were verified in the last event. You can see one be deducted from your account with registrationToken[msg.sender]--. The purpose of the registration tokens is that you can easily mix them, so that your personhood is not traceable from month to month.

function register() public scheduler {
    require(isReg(msg.sender) == false && data[schedule].tokens[1].balanceOf[msg.sender] >= 1);
    data[schedule].tokens[1].balanceOf[msg.sender]--;
    uint id;
    if(data[schedule].shuffler.length != 0) {
        id += getRandomNumber() % data[schedule].shuffler.length;
        data[schedule].registry[data[schedule].shuffler[id]].id = data[schedule].shuffler.length;
        data[schedule].shuffler.push(data[schedule].shuffler[id]);
    }
    else data[schedule].shuffler.push();

    data[schedule].shuffler[id] = msg.sender;
    data[schedule].registry[msg.sender] = Reg(true, id, new bool[](1), false);
    data[schedule+period].tokens[0].balanceOf[msg.sender]++;
}

To "immigrate" when you are "out-group" from the "nation", you acquire your permit and then use immigrate(). The "permit" is given out by anyone in the Anonymous "nation".

function immigrate() public scheduler {
    require(isReg(msg.sender) == false && data[schedule].tokens[0].balanceOf[msg.sender] >= 1);
    data[schedule].tokens[0].balanceOf[msg.sender]--;
    data[schedule].registry[msg.sender] = Reg(false, getRandomNumber(), new bool[](2), false);
    data[schedule].tokens[0].balanceOf[data[schedule-period].shuffler[getRandomNumber()%data[schedule-period].shuffler.length]]++;
}

When you opt-in, you are "judged" by another pair who acts as a "court", 2-on-1. This same "court" process is also used to solve any problem that can occur within the pairs, through the function dispute().

function dispute() public scheduler {
    require(data[schedule-period].registry[msg.sender].rank == true);
    uint pair = data[schedule-period].registry[msg.sender].id/2;
    require(pairVerified(pair) == false);
    data[schedule-period].disputed[pair] = true;
}

If your pair has been disputed, you use the function reassign().

function reassign() public scheduler {
    require(data[schedule-period].disputed[getPair(msg.sender)] == true);
    delete data[schedule-period].registry[msg.sender];
    data[schedule-period].registry[msg.sender] = Reg(false, getRandomNumber(), new bool[](2), false);
}

Anonymous is built around a simple schedule, and all actions are contained within the period your "proof-of-unique-human" is valid.

uint schedule;
uint period = 28 days;
uint hour;

function pseudonymEvent() public view returns (uint) { return schedule + period + hour * 3600; }
modifier scheduler() {
    if(block.timestamp > pseudonymEvent()) {
        schedule += period;
        hour = getRandomNumber()%24;
    }
    _;
}

The function to initiate the Anonymous network is very simple. As long as the system is dormant, the person calling the function gets 2**256-1 registration tokens, maximum number possible, and can invite as many people as they want to generation 0.

function initiateNetwork() public scheduler {
    require(data[schedule-period].shuffler.length < 2);
    schedule = 198000 + ((block.timestamp - 198000)/ 7 days) * 7 days - 21 days;
    hour = getRandomNumber()%24;
    data[schedule].tokens[1].balanceOf[msg.sender] = maxValue;
    data[schedule-period].shuffler = new address[](2);
}    

The pseudonym event is scheduled to be on the weekend of the 7-day week, centered around a Saturday at 07:00 UTC, keeping all events on the weekend for all time zones for all time. initateNetwork() sets schedule so that it is as close as possible to the next event that fits that schedule.

These are the data types that are necessary to provide proof-of-unique-human for a practically infinite population (on Ethereum with 2^160 addresses, a trillion trillion trillion trillion people. )

struct Reg {
    bool rank;
    uint id;
    bool[] signatures;
    bool verified;
}
struct Token {
    mapping(address => uint) balanceOf;
    mapping (address => mapping (address => uint)) allowed;
}
struct Data {
    mapping(address => Reg) registry;
    address[] shuffler;
    mapping(uint => bool) disputed;
    Token[3] tokens;
    uint personhoodNonce;
    mapping(address => uint) proofOfPersonhood;
}
mapping(uint => Data) data;

Verification is likewise simple. In Anonymous, you can upload signatures from people who verified you at any time after an event up to the next event, 28 days. Very simple. Ideally, as fast as possible, but there are no time constraints other than 4 weeks, because there does not need to be any other constraints so there is no reason to add any.

function verify(address _account, address _signer) internal {
    require(_account != _signer && data[schedule-period].registry[_signer].rank == true);
    uint pair = getPair(_account);
    require(data[schedule-period].disputed[pair] == false && pair == getPair(_signer));
    if(data[schedule-period].registry[_account].rank == true) data[schedule-period].registry[_account].signatures[0] = true;
    else data[schedule-period].registry[_account].signatures[data[schedule-period].registry[_signer].id%2] = true;
}
function verifyAccount(address _account) public scheduler {
    verify(_account, msg.sender);
}
function uploadSignature(address _account, bytes memory _signature) public scheduler {

    bytes32 r;
    bytes32 s;
    uint8 v;

    assembly {
        r := mload(add(_signature,0x20))
        s := mload(add(_signature,0x40))
        v := and(mload(add(_signature, 0x41)), 0xFF)
    }
    if (v < 27) v += 27;

    bytes32 msgHash = keccak256(abi.encodePacked(_account, schedule));

    verify(_account, ecrecover(msgHash, v, r, s));
}

Once verified, and once you have mixed the "verifiedToken" you received, you lock your proof-of-unique-human for the 4 week period.

function lockProofOfPersonhood() public scheduler {
    require(data[schedule].tokens[2].balanceOf[msg.sender] >= 1);
    require(data[schedule].proofOfPersonhood[msg.sender] == 0);
    data[schedule].tokens[2].balanceOf[msg.sender]--;
    data[schedule].personhoodNonce++;
    data[schedule].proofOfPersonhood[msg.sender] = data[schedule].personhoodNonce;
    data[schedule].personhoodIndex[data[schedule].personhoodNonce] = msg.sender;
}

Other dApps can then verify your ownership of a proof-of-unique-human using verifyPersonhood(_account).

function verifyPersonhood(address _account, uint _backlog) public scheduler returns (uint) {
    return data[schedule-period*_backlog].proofOfPersonhood[_account];
}

The only attack vector is collusion attacks. They scale with an inverse square relationship, and the payoff is minimal unless a significant part of the population colludes. The pairs the colluders gains control of can be calculated with how many pairs they get majority in (both people in. ) Mathematically this is (colluders)/(population)^2, based on probability theory. The colluders control these pairs without a human appearing in event, which means they can simultaneously be verified at the border with the people assigned to those pairs. The bots controlled and overall people that are free to be verified at the border increases slightly with repeated attacks, but there is less and less increase for each round. The benefit of repeated attacks can be calculated with the recursive sequence bots_n = ((colluders + population * bots_{n-1})/(population + population * bots_{n-1}))^2 and it plateaus very close to (colluders/population)^2.

Reference implementation

contract PseudonymPairs {

    uint entropy;
    function getRandomNumber() internal returns (uint){ entropy = uint(keccak256(abi.encodePacked(now, msg.sender, blockhash(block.number - 1), entropy))); return entropy; }

    uint maxValue = 2**256-1;
    
    uint schedule;
    uint period = 28 days;
    uint hour;

    struct Reg {
        bool rank;
        uint id;
        bool[] signatures;
        bool verified;
    }
    struct Token {
        mapping(address => uint) balanceOf;
        mapping (address => mapping (address => uint)) allowed;
    }
    struct Data {
        mapping(address => Reg) registry;
        address[] shuffler;
        mapping(uint => bool) disputed;
        Token[3] tokens;
        uint personhoodNonce;
        mapping(address => uint) proofOfPersonhood;
        mapping(uint => address) personhoodIndex;
    }
    mapping(uint => Data) data;
    function pseudonymEvent() public view returns (uint) { return schedule + period + hour * 3600; }
    modifier scheduler() {
        if(block.timestamp > pseudonymEvent()) {
            schedule += period;
            hour = getRandomNumber()%24;
        }
        _;
    }
    function isReg(address _account) public view returns (bool) { return data[schedule].registry[_account].signatures.length != 0; }
    
    function register() public scheduler {
        require(isReg(msg.sender) == false && data[schedule].tokens[1].balanceOf[msg.sender] >= 1);
        data[schedule].tokens[1].balanceOf[msg.sender]--;
        uint id;
        if(data[schedule].shuffler.length != 0) {
            id += getRandomNumber() % data[schedule].shuffler.length;
            data[schedule].registry[data[schedule].shuffler[id]].id = data[schedule].shuffler.length;
            data[schedule].shuffler.push(data[schedule].shuffler[id]);
        }
        else data[schedule].shuffler.push();

        data[schedule].shuffler[id] = msg.sender;
        data[schedule].registry[msg.sender] = Reg(true, id, new bool[](1), false);
        data[schedule+period].tokens[0].balanceOf[msg.sender]++;
    }
    function immigrate() public scheduler {
        require(isReg(msg.sender) == false && data[schedule].tokens[0].balanceOf[msg.sender] >= 1);
        data[schedule].tokens[0].balanceOf[msg.sender]--;
        data[schedule].registry[msg.sender] = Reg(false, getRandomNumber(), new bool[](2), false);
        data[schedule].tokens[0].balanceOf[data[schedule-period].shuffler[getRandomNumber()%data[schedule-period].shuffler.length]]++;
    }
    function transferRegistrationKey(address _to) public scheduler {
        require(isReg(msg.sender) == true && isReg(_to) == false);
        if(data[schedule].registry[msg.sender].rank == true) data[schedule].shuffler[data[schedule].registry[msg.sender].id] = _to;
        data[schedule].registry[_to] = data[schedule].registry[msg.sender];
        delete data[schedule].registry[msg.sender];
    }
    function pairVerified(uint _pair) internal view returns (bool) {
        address peer1 = data[schedule-period].shuffler[_pair];
        address peer2 = data[schedule-period].shuffler[_pair + 1];
        return (data[schedule-period].registry[peer1].signatures[0] == true && data[schedule-period].registry[peer2].signatures[0] == true);
    }
    function dispute() public scheduler {
        require(data[schedule-period].registry[msg.sender].rank == true);
        uint pair = data[schedule-period].registry[msg.sender].id/2;
        require(pairVerified(pair) == false);
        data[schedule-period].disputed[pair] = true;
    }
    function getPair(address _account) internal view returns (uint) {
        if(data[schedule-period].registry[_account].rank == true) return data[schedule-period].registry[_account].id/2;
        return data[schedule-period].registry[msg.sender].id%(data[schedule-period].shuffler.length/2);
    }
    function reassign() public scheduler {
        require(data[schedule-period].disputed[getPair(msg.sender)] == true);
        delete data[schedule-period].registry[msg.sender];
        data[schedule-period].registry[msg.sender] = Reg(false, getRandomNumber(), new bool[](2), false);
    }
    function verifyPersonhood(address _account, uint _backlog) public scheduler returns (uint) {
        return data[schedule-period*_backlog].proofOfPersonhood[_account];
    }
    function getPersonhoodOwner(uint _personhoodID, uint _backlog) public scheduler returns (address) {
        return data[schedule-period*_backlog].personhoodIndex[_personhoodID];
    }
    function getPopulation(uint _backlog) public scheduler returns (uint) { 
        return data[schedule-period*_backlog].personhoodNonce; 
    }
    function lockProofOfPersonhood() public scheduler {
        require(data[schedule].tokens[2].balanceOf[msg.sender] >= 1);
        require(data[schedule].proofOfPersonhood[msg.sender] == 0);
        data[schedule].tokens[2].balanceOf[msg.sender]--;
        data[schedule].personhoodNonce++;
        data[schedule].proofOfPersonhood[msg.sender] = data[schedule].personhoodNonce;
        data[schedule].personhoodIndex[data[schedule].personhoodNonce] = msg.sender;
    }
    function transferPersonhoodKey(address _account) public scheduler {
        require(data[schedule].proofOfPersonhood[_account] == 0 && _account != msg.sender);
        data[schedule].proofOfPersonhood[_account] = data[schedule].proofOfPersonhood[msg.sender];
        data[schedule].personhoodIndex[data[schedule].proofOfPersonhood[msg.sender]] = _account;
        delete data[schedule].proofOfPersonhood[msg.sender];
    }
    function collectPersonhood() public scheduler {
        require(data[schedule-period].registry[msg.sender].verified = false);
        require(pairVerified(getPair(msg.sender)) == true);
        if(data[schedule-period].registry[msg.sender].rank == false) require(data[schedule-period].registry[msg.sender].signatures[0] == true && data[schedule-period].registry[msg.sender].signatures[1] == true);
        data[schedule].tokens[1].balanceOf[msg.sender]++;
        data[schedule].tokens[2].balanceOf[msg.sender]++;
        data[schedule-period].registry[msg.sender].verified = true;
    }
    function verify(address _account, address _signer) internal {
        require(_account != _signer && data[schedule-period].registry[_signer].rank == true);
        uint pair = getPair(_account);
        require(data[schedule-period].disputed[pair] == false && pair == getPair(_signer));
        if(data[schedule-period].registry[_account].rank == true) data[schedule-period].registry[_account].signatures[0] = true;
        else data[schedule-period].registry[_account].signatures[data[schedule-period].registry[_signer].id%2] = true;
    }
    function verifyAccount(address _account) public scheduler {
	    verify(_account, msg.sender);
    }
    function uploadSignature(address _account, bytes memory _signature) public scheduler {

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(_signature,0x20))
            s := mload(add(_signature,0x40))
            v := and(mload(add(_signature, 0x41)), 0xFF)
        }
        if (v < 27) v += 27;

        bytes32 msgHash = keccak256(abi.encodePacked(_account, schedule));

        verify(_account, ecrecover(msgHash, v, r, s));
    }
    function transfer(address _from, address _to, uint _value, uint _token) internal {
        require(data[schedule].tokens[_token].balanceOf[_from] >= _value);
        data[schedule].tokens[_token].balanceOf[_from] -= _value;
        data[schedule].tokens[_token].balanceOf[_to] += _value;
    }
    function delegate(address _spender, uint _value, uint _token) internal {
        data[schedule].tokens[_token].allowed[msg.sender][_spender] = _value;
    }
    function collect(address _from, address _to, uint _value, uint _token) internal {
        require(data[schedule].tokens[_token].allowed[_from][msg.sender] >= _value);
        transfer(_from, _to, _value, _token);
        data[schedule].tokens[_token].allowed[_from][msg.sender] -= _value;
    }
    function transferImmigrationToken(address _to, uint _value) public scheduler { transfer(msg.sender, _to, _value, 0); }
    function transferRegistrationToken(address _to, uint _value) public scheduler { transfer(msg.sender, _to, _value, 1); }
    function transferVerifiedToken(address _to, uint _value) public scheduler { transfer(msg.sender, _to, _value, 2); }
    function delegateImmigrationToken(address _spender, uint _value) public scheduler { delegate(_spender, _value, 0); }
    function delegateRegistrationToken(address _spender, uint _value) public scheduler { delegate(_spender, _value, 1); }
    function delegateVerifiedToken(address _spender, uint _value) public scheduler { delegate(_spender, _value, 2); }
    function collectImmigrationToken(address _from, address _to, uint _value) public scheduler { collect(_from, _to, _value, 0); }
    function collectRegistrationToken(address _from, address _to, uint _value) public scheduler { collect(_from, _to, _value, 1); }
    function collectVerifiedToken(address _from, address _to, uint _value) public scheduler { collect(_from, _to, _value, 2); }

    function initiateNetwork() public scheduler {
        require(data[schedule-period].shuffler.length < 2);
        schedule = 198000 + ((block.timestamp - 198000)/ 7 days) * 7 days - 21 days;
        hour = getRandomNumber()%24;
        data[schedule].tokens[1].balanceOf[msg.sender] = maxValue;
        data[schedule-period].shuffler = new address[](2);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment