-
Notifications
You must be signed in to change notification settings - Fork 68
IdentityManager - Identity factory with multiple devices and singleton controller support #17
Changes from 25 commits
5dfc653
32380cd
f4ec8d6
048611e
4a6da21
d289ce1
9d4fb31
b5f1771
09ab55c
875ed99
ce9ae06
03a40ff
03a546d
9f8767e
61097de
eca4abc
3ef76aa
e581692
fd749c0
4f84f01
5346800
e2e6420
b266633
8b4d35a
91fb8bf
5dd749a
e8cc4e0
44a7778
d3dab73
200c794
f762602
2f96523
4bbf91e
0c5d59b
e528dd3
f5ff249
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
pragma solidity 0.4.8; | ||
import "./Proxy.sol"; | ||
|
||
contract IdentityManager { | ||
uint adminTimeLock; | ||
uint userTimeLock; | ||
uint adminRate; | ||
|
||
event IdentityCreated( | ||
address indexed identity, | ||
address indexed creator, | ||
address owner, | ||
address indexed recoveryKey); | ||
|
||
event OwnerAdded( | ||
address indexed identity, | ||
address indexed owner, | ||
address instigator); | ||
|
||
event OwnerRemoved( | ||
address indexed identity, | ||
address indexed owner, | ||
address instigator); | ||
|
||
event RecoveryChanged( | ||
address indexed identity, | ||
address indexed recoveryKey, | ||
address instigator); | ||
|
||
event MigrationInitiated( | ||
address indexed identity, | ||
address indexed newIdManager, | ||
address instigator); | ||
|
||
event MigrationCanceled( | ||
address indexed identity, | ||
address indexed newIdManager, | ||
address instigator); | ||
|
||
event MigrationFinalized( | ||
address indexed identity, | ||
address indexed newIdManager, | ||
address instigator); | ||
|
||
mapping(address => mapping(address => uint)) owners; | ||
mapping(address => address) recoveryKeys; | ||
mapping(address => mapping(address => uint)) limiter; | ||
mapping(address => uint) migrationInitiated; | ||
mapping(address => address) migrationNewAddress; | ||
|
||
modifier onlyOwner(address identity) { | ||
if (owners[identity][msg.sender] > 0 && (owners[identity][msg.sender] + userTimeLock) <= now ) _ ; | ||
else throw; | ||
} | ||
|
||
modifier onlyOlderOwner(address identity) { | ||
if (owners[identity][msg.sender] > 0 && (owners[identity][msg.sender] + adminTimeLock) <= now) _ ; | ||
else throw; | ||
} | ||
|
||
modifier onlyRecovery(address identity) { | ||
if (recoveryKeys[identity] == msg.sender) _ ; | ||
else throw; | ||
} | ||
|
||
modifier rateLimited(address identity) { | ||
if (limiter[identity][msg.sender] < (now - adminRate)) { | ||
limiter[identity][msg.sender] = now; | ||
_ ; | ||
} else throw; | ||
} | ||
|
||
// Instantiate IdentityManager with the following limits: | ||
// - userTimeLock - Time before new owner can control proxy | ||
// - adminTimeLock - Time before new owner can add/remove owners | ||
// - adminRate - Time period used for rate limiting a given key for admin functionality | ||
function IdentityManager(uint _userTimeLock, uint _adminTimeLock, uint _adminRate) { | ||
adminTimeLock = _adminTimeLock; | ||
userTimeLock = _userTimeLock; | ||
adminRate = _adminRate; | ||
} | ||
|
||
// Factory function | ||
// gas 289,311 | ||
function CreateIdentity(address owner, address recoveryKey) { | ||
if (recoveryKey == address(0)) throw; | ||
Proxy identity = new Proxy(); | ||
owners[identity][owner] = now - adminTimeLock; // This is to ensure original owner has full power from day one | ||
recoveryKeys[identity] = recoveryKey; | ||
IdentityCreated(identity, msg.sender, owner, recoveryKey); | ||
} | ||
|
||
// An identity Proxy can use this to register itself with the IdentityManager | ||
// Note they also have to change the owner of the Proxy over to this, but after calling this | ||
function registerIdentity(address owner, address recoveryKey) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oed @christianlundkvist this is what I wrote to do the transfer. Isn't this what you meant? We would still have to do the actual transfer of the proxy as a second transaction. But that would just use forwardTo I thought. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The transfer of the ownership of the proxy needs to use a special function in the proxy contract:
so no way to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @christianlundkvist You mean for transfering an proxy away from the IdentityManager? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But yes, we need a function to transfer the identity away from the IdentityManager There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok I'll have a look @christianlundkvist There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oed Yep what I meant was we need a function in the IdentityManager to transfer a proxy to another IdentityManager. |
||
if (recoveryKey == address(0)) throw; | ||
if (owners[msg.sender][owner] > 0 || recoveryKeys[msg.sender] > 0 ) throw; // Deny any funny business | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First check here is not needed. As we enforce the recoveryKey invariant, this is not an issue. |
||
owners[msg.sender][owner] = now - adminTimeLock; // This is to ensure original owner has full power from day one | ||
recoveryKeys[msg.sender] = recoveryKey; | ||
IdentityCreated(msg.sender, msg.sender, owner, recoveryKey); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to have another event here, like |
||
} | ||
|
||
// Primary forward function | ||
function forwardTo(Proxy identity, address destination, uint value, bytes data) onlyOwner(identity) { | ||
identity.forward(destination, value, data); | ||
} | ||
|
||
// an owner can add a new device instantly | ||
function addOwner(Proxy identity, address newOwner) onlyOlderOwner(identity) rateLimited(identity) { | ||
owners[identity][newOwner] = now; | ||
OwnerAdded(identity, newOwner, msg.sender); | ||
} | ||
|
||
// a recovery key owner can add a new device with 'adminRate' wait time | ||
function addOwnerFromRecovery(Proxy identity, address newOwner) onlyRecovery(identity) rateLimited(identity) { | ||
if (owners[identity][newOwner] > 0) throw; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be if (isOwner(identity, newOwner)) throw; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point! |
||
owners[identity][newOwner] = now; | ||
OwnerAdded(identity, newOwner, msg.sender); | ||
} | ||
|
||
// an owner can remove another owner instantly | ||
function removeOwner(Proxy identity, address owner) onlyOlderOwner(identity) rateLimited(identity) { | ||
owners[identity][owner] = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe use delete keyword for clarity? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, thanks! |
||
OwnerRemoved(identity, owner, msg.sender); | ||
} | ||
|
||
// an owner can change the recoverykey whenever they want to | ||
function changeRecovery(Proxy identity, address recoveryKey) onlyOlderOwner(identity) rateLimited(identity) { | ||
if (recoveryKey == address(0)) throw; | ||
recoveryKeys[identity] = recoveryKey; | ||
RecoveryChanged(identity, recoveryKey, msg.sender); | ||
} | ||
|
||
// an owner can migrate away to a new IdentityManager | ||
function initiateMigration(Proxy identity, address newIdManager) onlyOlderOwner(identity) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oed Based on the other changes checking for null recovery keys, we should do the same here. Basically if newIdManager is null it should fail. |
||
if (newIdManager == address(0)) throw; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oed could use the validAddress modifier here also. |
||
migrationInitiated[identity] = now; | ||
migrationNewAddress[identity] = newIdManager; | ||
MigrationInitiated(identity, newIdManager, msg.sender); | ||
} | ||
|
||
// any owner can cancel a migration | ||
function cancelMigration(Proxy identity) onlyOwner(identity) { | ||
address canceledManager = migrationNewAddress[identity]; | ||
migrationInitiated[identity] = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe delete here also |
||
migrationNewAddress[identity] = 0; | ||
MigrationCanceled(identity, canceledManager, msg.sender); | ||
} | ||
|
||
// owner needs to finalize migration once adminTimeLock time has passed | ||
// WARNING: before transfering to a new address, make sure this address is "ready to recieve" the proxy. | ||
// Not doing so risks the proxy becoming stuck. | ||
function finalizeMigration(Proxy identity) onlyOlderOwner(identity) { | ||
if (migrationInitiated[identity] > 0 && migrationInitiated[identity] + adminTimeLock < now) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Explanatory comment on my above comment. If |
||
address newIdManager = migrationNewAddress[identity]; | ||
migrationInitiated[identity] = 0; | ||
migrationNewAddress[identity] = 0; | ||
identity.transfer(newIdManager); | ||
MigrationFinalized(identity, newIdManager, msg.sender); | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
const IdentityManager = artifacts.require('./IdentityManager.sol') | ||
|
||
module.exports = function (deployer) { | ||
deployer.deploy(IdentityManager) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know about this
address(0)
. Nice.