When you register for Pseudonym Pairs, 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. Why? Why not, it gives everyone a new chance in life every month, prejudice becomes impossible, discrimination also impossible, etc. There is no advantage in associating personal data to these personhoods, since “who you are “ does not have to be defined for the verification procedure, and there are many advantages with the anonymity. For example, you now live in a world of totalitarian surveillance, wouldn’t it be good if you at the same time could enjoy some integrity? Pseudonym Pairs solves that. Good.
function register() public scheduler {
require(d[l()].registry[msg.sender].id == 0 && d[l()-1].mixer.registrationToken[msg.sender] >= 1);
d[l()-1].mixer.registrationToken[msg.sender]--;
uint pseudonyms = d[l()].counter[0];
uint id = 1;
if(pseudonyms != 0) {
id += getRandomNumber() % pseudonyms;
shuffleID(true, pseudonyms + 1, id);
}
assignID(l(), msg.sender, true, id);
d[l()].counter[0]++;
d[l()].borderToken[msg.sender]++;
}
The game theory of Pseudonym Pairs is that everyone is in pairs, unless there is a problem, and then people go into "mob rule", you are arbitrated by another pair who acts as a "judge" or "court", 2-on-1. This same "court" process is also used for immigration.
function breakPair() public scheduler {
}
In Pseudonym Pairs, you need a permit to immigrate. After you acquire a permit, you present yourself to the "border police". This "border police" is a random pair of two people in the "nation", and, the "permit" is given out by anyone in the "nation", using this function, a basic transfer() function similar to the old ERC20 standard token on Ethereum.
Very simple. The code below runs perfectly well on a public ledger like Ethereum, and can serve up to 2^160 people, a trillion trillion trillion trillion people, with just 4 lines of code. Buckminster Fuller called that "ephemeralization", the ability to do more with less. You can today do what required vast manpower of bureaucrats, in just a few lines of code, using smart contract technology.
function transferBorderToken(address _to, uint _amount) public scheduler {
require(d[l()-1].borderToken[msg.sender] >= _amount);
d[l()-1].borderToken[msg.sender] -= _amount;
d[l()-1].borderToken[_to] += _amount;
}
The "virtual border" around Pseudonym Pairs is very simple. You are inside as long as you are continuously verified, verification lets you register for the next event. To "immigrate" when you are "out-group" from the nation, you acquire your permit and then use immigrate()
.
function immigrate() public scheduler {
require(d[l()].registry[msg.sender].id == 0 && d[l()-1].borderToken[msg.sender] >= 1);
d[l()-1].borderToken[msg.sender]--;
uint pairs = d[l()].counter[0]/2; uint courts = d[l()].counter[1];
uint court;
if(pairs > courts) court = selectCourt(l(), pairs, courts);
else court = courts + 1;
d[l()].assignCourt[courts+1] = court;
if(courts > 0) {
uint shuffled = d[l()].assignCourt[1 + getRandomNumber() % courts];
shuffleID(true, court, shuffled);
court = shuffled;
}
assignID(l(), msg.sender, false, court);
issueBorderToken();
d[l()].counter[1]++;
}
Whenever anyone registers themselves with immigrate()
to be inspected by the border police, a random person "in-group" gets the ability to issue one more permit (allowing the network to grow as fast as it wants to in the beginning, no defined limit. )
function issueBorderToken() internal {
uint pseudonyms = d[l()-1].counter[0]; uint courts = d[l()].counter[1]%pseudonyms;
uint id = 1 + courts + getRandomNumber() % (pseudonyms - courts);
uint peer;
if(d[l()-1].borderTokenLottery[id] == 0) peer = id;
else peer = d[l()-1].borderTokenLottery[id];
d[l()-1].borderToken[d[l()-1].registryIndex[true][peer]]++;
if(d[l()-1].borderTokenLottery[courts+1] == 0) d[l()-1].borderTokenLottery[id] = courts+1;
else d[l()-1].borderTokenLottery[id] = d[l()-1].borderTokenLottery[courts+1];
d[l()-1].borderTokenLottery[courts+1] = peer;
}
As the processes in assignID()
and shuffleID()
are used by both register()
and immigrate()
, and assignID()
also used in transferRegistrationKey()
and reassign()
, they are defined as their own methods.
function assignID(uint _l, address _account, bool _rank, uint _id) internal {
d[_l].registry[_account] = Reg(_rank, _id, new bool[](2 - boolToUint(_rank)), false);
d[_l].registryIndex[_rank][_id] = _account;
}
function shuffleID(bool _rank, uint _id, uint _shuffled) internal {
d[l()].registryIndex[_rank][_id] = d[l()].registryIndex[_rank][_shuffled];
d[l()].registry[d[l()].registryIndex[_rank][_shuffled]].id = _id;
}
Pseudonym Pairs is built around a simple schedule, and all actions are contained within the period your "proof-of-personhood" is valid, set to 28 days in my design, but, can be of arbitrary length.
uint schedulePeriod;
uint[] clockwork;
uint hour;
function scheduleHour() internal {
if(clockwork.length == 0) clockwork.length = 24;
uint randomHour = getRandomNumber() % clockwork.length;
if(clockwork[randomHour] == 0) hour = randomHour;
else hour = clockwork[randomHour];
if(clockwork[clockwork.length - 1] == 0) clockwork[randomHour] = clockwork.length - 1;
else clockwork[randomHour] = clockwork[clockwork.length - 1];
clockwork.length--;
}
Everything you do in Pseudonym Pairs activates “scheduler” that schedules events. Very simple. If you for example register yourself for a month with register()
, and it turns out that the month has just begun and you are the first (out of 7 billion people..) to activate “scheduler”, then you will schedule when the next event happens (you will pay maybe 0.0001 USD extra for “register()” in that case to cover that process as well, shit happens. )
modifier scheduler() {
if(block.timestamp > schedulePeriod + hour * 3600) {
schedulePeriod += 28 days;
scheduleHour();
d.length++;
}
_;
}
The reassign()
function in Pseudonym Pairs that you use if you split your pair (how pairs are possible, it becomes mob rule, 2-on-1, if there is any problem at all) conveniently also sorts the "odd" person if there is an odd number of people who register for the event, pseudonyms%2 == 1, like, 7 billion 391 million 249 thousand 031 people, a single person there would not be in a pair, and require(d[l()-1].registryIndex[true][2*getPair(msg.sender)] == address(0));
recognizes that, conveniently. Means it does not have to be solved with additional function or code. I was assuming it needed some additional code, before I noticed it did not.
function reassign() public scheduler {
require(d[l()-1].registry[msg.sender].id != 0 && d[l()-1].registryIndex[true][2*getPair(msg.sender)] == address(0));
Pseudonym Pairs is, because it was easiest way, structured so that the personhoods are numbered.
function verifyPersonhood(address _account) public scheduler returns (uint) {
require(d[l()-1].proofOfPersonhood[_account] != 0);
return d[l()-1].proofOfPersonhood[_account];
}
The function to initiate Pseudonym Pairs network is very simple. As long as more than 28 days + 23 hours has passed since schedulePeriod
, 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. 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 schedulePeriod
so that it is as close as possible to the next event that fits that schedule.
function initiateNetwork() public {
require(block.timestamp > schedulePeriod + 23 hours + 28 days);
schedulePeriod = 198000 + ((block.timestamp - 198000)/ 7 days) * 7 days - 21 days;
delete clockwork;
d.length++;
d[l()].mixer.registrationToken[msg.sender] = 2**256-1;
}
These are the data types that are necessary to provide proof-of-personhood 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 Mixer {
mapping(address => uint) registrationToken;
mapping(address => uint) verifiedToken;
}
struct PseudonymEvent {
mapping(address => Reg) registry;
mapping(bool => mapping(uint => address)) registryIndex;
uint[2] counter;
mapping(uint => uint) assignCourt;
mapping(uint => uint) assignCourtIndex;
uint splitPairs;
mapping(uint => uint) borderTokenLottery;
mapping(address => uint) borderToken;
Mixer mixer;
uint personhoodNonce;
mapping(address => uint) proofOfPersonhood;
}
PseudonymEvent[] d;
function l() internal view returns (uint) { return d.length - 1; }
Verification is likewise simple. In Pseudonym Pairs, 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 {
uint pairID = getPair(_account);
require(d[l()-1].registryIndex[true][pairID*2] != address(0) && pairID == getPair(_signer) && d[l()-1].registry[_signer].rank == true);
if(d[l()-1].registry[_account].rank == true) d[l()-1].registry[_account].signatures[0] = true;
else d[l()-1].registry[_account].signatures[d[l()-1].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, l()));
verify(_account, ecrecover(msgHash, v, r, s));
}