Last active
November 29, 2016 18:33
-
-
Save zmitton/0ea06dd3ef2c53b1d436383eff2667cd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Lib1 deployed at Ropsten : 0x09c3aAE78AA34c91B56fc6AbFE2e5a0AbceAaD45 | |
//IdentityFactory deployed at Ropsten : 0xE8fe77d1576d0972d453b49bfAA84d716173D133 | |
pragma solidity ^0.4.4; | |
library Lib1{ | |
function findAddress(address a, address[] storage arry) returns (int){ | |
for (uint i = 0 ; i < arry.length ; i++){ | |
if(arry[i] == a){return int(i);} | |
} | |
return -1; | |
} | |
function removeAddress(uint i, address[] storage arry){ | |
uint lengthMinusOne = arry.length - 1; | |
arry[i] = arry[lengthMinusOne]; | |
delete arry[lengthMinusOne]; | |
arry.length = lengthMinusOne; | |
} | |
} | |
// A base Owned contract | |
contract Owned { | |
address public owner; | |
modifier onlyOwner(){ if (isOwner(msg.sender)) _; } | |
modifier ifOwner(address sender) { if(isOwner(sender)) _; } | |
function Owned(){ | |
owner = msg.sender; | |
} | |
function isOwner(address addr) public returns(bool) { return addr == owner; } | |
function transfer(address _owner) onlyOwner { | |
owner = _owner; | |
} | |
} | |
// The core proxy facade | |
// - is owned by a user or implementation contract | |
// - only forwards transactions for its owner | |
contract Proxy is Owned { | |
event Forwarded ( | |
address indexed destination, | |
uint value, | |
bytes data | |
); | |
function forward(address destination, uint value, bytes data) onlyOwner { | |
// If a contract tries to CALL or CREATE a contract with either | |
// (i) insufficient balance, or (ii) stack depth already at maximum (1024), | |
// the sub-execution and transfer do not occur at all, no gas gets consumed, and 0 is added to the stack. | |
// see: https://github.com/ethereum/wiki/wiki/Subtleties#exceptional-conditions | |
if (!destination.call.value(value)(data)) { | |
throw; | |
} | |
Forwarded(destination, value, data); | |
} | |
} | |
contract RecoverableController { | |
uint public version; | |
Proxy public proxy; | |
address public userKey; | |
address public proposedUserKey; | |
uint public proposedUserKeyPendingUntil; | |
address public recoveryKey; | |
address public proposedRecoveryKey; | |
uint public proposedRecoveryKeyPendingUntil; | |
address public proposedController; | |
uint public proposedControllerPendingUntil; | |
uint public shortTimeLock;// use 900 for 15 minutes | |
uint public longTimeLock; // use 259200 for 3 days | |
event RecoveryEvent(string action, address initiatedBy); | |
modifier onlyUserKey() { if (msg.sender == userKey) _; } | |
modifier onlyRecoveryKey() { if (msg.sender == recoveryKey) _; } | |
function RecoverableController(address proxyAddress, address _userKey, uint _longTimeLock, uint _shortTimeLock) { | |
version = 1; | |
proxy = Proxy(proxyAddress); | |
userKey = _userKey; | |
shortTimeLock = _shortTimeLock; | |
longTimeLock = _longTimeLock; | |
recoveryKey = msg.sender; | |
} | |
function forward(address destination, uint value, bytes data) onlyUserKey { | |
proxy.forward(destination, value, data); | |
} | |
//pass 0x0 to cancel | |
function signRecoveryChange(address _proposedRecoveryKey) onlyUserKey{ | |
proposedRecoveryKeyPendingUntil = now + longTimeLock; | |
proposedRecoveryKey = _proposedRecoveryKey; | |
RecoveryEvent("signRecoveryChange", msg.sender); | |
} | |
function changeRecovery() { | |
if(proposedRecoveryKeyPendingUntil < now && proposedRecoveryKey != 0x0){ | |
recoveryKey = proposedRecoveryKey; | |
delete proposedRecoveryKey; | |
} | |
} | |
//pass 0x0 to cancel | |
function signControllerChange(address _proposedController) onlyUserKey{ | |
proposedControllerPendingUntil = now + longTimeLock; | |
proposedController = _proposedController; | |
RecoveryEvent("signControllerChange", msg.sender); | |
} | |
function changeController() { | |
if(proposedControllerPendingUntil < now && proposedController != 0x0){ | |
proxy.transfer(proposedController); | |
suicide(proposedController); | |
} | |
} | |
//pass 0x0 to cancel | |
function signUserKeyChange(address _proposedUserKey) onlyUserKey{ | |
proposedUserKeyPendingUntil = now + shortTimeLock; | |
proposedUserKey = _proposedUserKey; | |
RecoveryEvent("signUserKeyChange", msg.sender); | |
} | |
function changeUserKey(){ | |
if(proposedUserKeyPendingUntil < now && proposedUserKey != 0x0){ | |
userKey = proposedUserKey; | |
delete proposedUserKey; | |
RecoveryEvent("changeUserKey", msg.sender); | |
} | |
} | |
function changeRecoveryFromRecovery(address _recoveryKey) onlyRecoveryKey{ recoveryKey = _recoveryKey; } | |
function changeUserKeyFromRecovery(address _userKey) onlyRecoveryKey{ | |
delete proposedUserKey; | |
userKey = _userKey; | |
} | |
} | |
contract RecoveryQuorum { | |
RecoverableController public controller; | |
address[] public delegateAddresses; // needed for iteration of mapping | |
mapping (address => Delegate) public delegates; | |
struct Delegate{ | |
uint deletedAfter; // delegate exists if not 0 | |
uint pendingUntil; | |
address proposedUserKey; | |
} | |
event RecoveryEvent(string action, address initiatedBy); | |
modifier onlyUserKey(){ if (msg.sender == controller.userKey()) _; } | |
function RecoveryQuorum(address _controller, address[] _delegates){ | |
controller = RecoverableController(_controller); | |
for(uint i = 0; i < _delegates.length; i++){ | |
delegateAddresses.push(_delegates[i]); | |
delegates[_delegates[i]] = Delegate({proposedUserKey: 0x0, pendingUntil: 0, deletedAfter: 31536000000000}); | |
} | |
} | |
function signUserChange(address proposedUserKey) { | |
if(delegateRecordExists(delegates[msg.sender])) { | |
delegates[msg.sender].proposedUserKey = proposedUserKey; | |
changeUserKey(proposedUserKey); | |
RecoveryEvent("signUserChange", msg.sender); | |
} | |
} | |
function changeUserKey(address newUserKey) { | |
if(collectedSignatures(newUserKey) >= neededSignatures()){ | |
controller.changeUserKeyFromRecovery(newUserKey); | |
for(uint i = 0 ; i < delegateAddresses.length ; i++){ | |
//remove any pending delegates after a recovery | |
if(delegates[delegateAddresses[i]].pendingUntil > now){ | |
delegates[delegateAddresses[i]].deletedAfter = now; | |
} | |
delete delegates[delegateAddresses[i]].proposedUserKey; | |
} | |
} | |
} | |
function replaceDelegates(address[] delegatesToRemove, address[] delegatesToAdd) onlyUserKey{ | |
for(uint i = 0 ; i < delegatesToRemove.length ; i++){ | |
removeDelegate(delegatesToRemove[i]); | |
} | |
garbageCollect(); | |
for(uint j = 0 ; j < delegatesToAdd.length ; j++){ | |
addDelegate(delegatesToAdd[j]); | |
} | |
RecoveryEvent("replaceDelegates", msg.sender); | |
} | |
function collectedSignatures(address _proposedUserKey) returns (uint signatures){ | |
for(uint i = 0 ; i < delegateAddresses.length ; i++){ | |
if (delegateHasValidSignature(delegates[delegateAddresses[i]]) && delegates[delegateAddresses[i]].proposedUserKey == _proposedUserKey){ | |
signatures++; | |
} | |
} | |
} | |
function getAddresses() constant returns (address[]){ return delegateAddresses; } | |
function neededSignatures() returns (uint){ | |
uint currentDelegateCount; //always 0 at this point | |
for(uint i = 0 ; i < delegateAddresses.length ; i++){ | |
if(delegateIsCurrent(delegates[delegateAddresses[i]])){ currentDelegateCount++; } | |
} | |
return currentDelegateCount/2 + 1; | |
} | |
function addDelegate(address delegate) private { | |
if(!delegateRecordExists(delegates[delegate]) && delegateAddresses.length < 15) { | |
delegates[delegate] = Delegate({proposedUserKey: 0x0, pendingUntil: now + controller.longTimeLock(), deletedAfter: 31536000000000}); | |
delegateAddresses.push(delegate); | |
} | |
} | |
function removeDelegate(address delegate) private { | |
if(delegates[delegate].deletedAfter > controller.longTimeLock() + now){ | |
//remove right away if they are still pending | |
if(delegates[delegate].pendingUntil > now){ | |
delegates[delegate].deletedAfter = now; | |
} else{ | |
delegates[delegate].deletedAfter = controller.longTimeLock() + now; | |
} | |
} | |
} | |
function garbageCollect() private{ | |
uint i = 0; | |
while(i < delegateAddresses.length){ | |
if(delegateIsDeleted(delegates[delegateAddresses[i]])){ | |
delegates[delegateAddresses[i]].deletedAfter = 0; | |
delegates[delegateAddresses[i]].pendingUntil = 0; | |
delegates[delegateAddresses[i]].proposedUserKey = 0; | |
Lib1.removeAddress(i, delegateAddresses); | |
}else{i++;} | |
} | |
} | |
function delegateRecordExists(Delegate d) private returns (bool){ | |
return d.deletedAfter != 0; | |
} | |
function delegateIsDeleted(Delegate d) private returns (bool){ | |
return d.deletedAfter <= now; //doesnt check record existence | |
} | |
function delegateIsCurrent(Delegate d) private returns (bool){ | |
return delegateRecordExists(d) && !delegateIsDeleted(d) && now > d.pendingUntil; | |
} | |
function delegateHasValidSignature(Delegate d) private returns (bool){ | |
return delegateIsCurrent(d) && d.proposedUserKey != 0x0; | |
} | |
} | |
contract IdentityFactory { | |
event IdentityCreated( | |
address indexed creator, | |
address proxy, | |
address controller, | |
address recoveryQuorum); | |
mapping(address => address) public senderToProxy; | |
function CreateProxyWithControllerAndRecovery(address userKey, address[] delegates, uint longTimeLock, uint shortTimeLock) { | |
Proxy proxy = new Proxy(); | |
RecoverableController controller = new RecoverableController(proxy, userKey, longTimeLock, shortTimeLock); | |
proxy.transfer(controller); | |
RecoveryQuorum recoveryQuorum = new RecoveryQuorum(controller, delegates); | |
controller.changeRecoveryFromRecovery(recoveryQuorum); | |
IdentityCreated(msg.sender, proxy, controller, recoveryQuorum); | |
senderToProxy[msg.sender] = proxy; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment