Code Monkey home page Code Monkey logo

uport-identity's Introduction

uPort Identity Contracts

npm CircleCI Join the chat at solidity-coverage

Contract documentation

This repository contains the contracts currently in use by uPort. This is also where you find the addresses of these contracts currently deployed on Mainnet and relevant test networks. Below you can find descriptions of each of the contracts and the rationale behind the design decisions.

Proxy | TxRelay | IdentityManager | MetaIdentityManager

  1. Using the contracts
  2. Testing the contracts
  3. Contract interactions
  4. Deploying contracts to a private network

Contract Deployments

Mainnet (id: 1)

Contract Address
IdentityManager 0x22a4d688748845e9d5d7394a0f05bc583adf4656
TxRelay 0xec2642cd5a47fd5cca2a8a280c3b5f88828aa578
MetaIdentityManager 0x27500ae27b6b6ad7de7d64b1def90f3e6e7ced47

Rinkeby testnet (id: 4)

Contract Address
IdentityManager 0x19aece3ae41ee33c30f331906b7e4bb578946a55
TxRelay 0xda8c6dce9e9a85e6f9df7b09b2354da44cb48331
MetaIdentityManager 0x87ea811785c4bd30fc104c2543cf8ed90f7eeec7

Kovan testnet (id: 42)

Contract Address
IdentityManager 0xdb55d40684e7dc04655a9789937214b493a2c2c6
TxRelay 0xa9235151d3afa7912e9091ab76a36cbabe219a0c
MetaIdentityManager 0x737f53c0cebf0acd1ea591685351b2a8580702a5

Ropsten testnet (id: 3)

Contract Address
IdentityManager 0x27500ae27b6b6ad7de7d64b1def90f3e6e7ced47
TxRelay 0xa5e04cf2942868f5a66b9f7db790b8ab662039d5
MetaIdentityManager 0xbdaf396ce9b9b9c42cd40d37e01b5dbd535cc960

Contributing

Want to contribute to uport-contracts? Cool, please read our contribution guidelines to get an understanding of the process we use for making changes to this repo.

uport-identity's People

Contributors

aldigjo avatar coder5876 avatar dependabot[bot] avatar eseoghene avatar localredhead avatar naterush avatar oed avatar pelle avatar zachferland avatar zmitton avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

uport-identity's Issues

Metatransactions have replay potential

// relay :: nonce :: destination :: data :: relayer

The keys that sign metatransactions are intended to be used for raw Ethereum transactions as well, especially when bypassing the relay altogether and sending transactions directly. Signed data can be rebroadcast as raw Ethereum transactions if the plaintext is formatted similarly. This should not be possible given the current metatransaction payload since the payload size differs from a raw transaction, but the safest option is to prefix the payload with data that will never be part of a valid transaction.

Recommendation

Consider using EIP 191 signed messages for metatransactions to guarantee protection from replaying as raw transactions.

Document gas heavy functions

replaceDelegates has a few loops including garbageCollect
(in RecoveryQuorum.sol#L47)

Since it's behavior has a wide range of gas consumption, the higher end usage should be documented. There should also be explicitly tests for "worst case" scenarios. A goal is to minimize the risk of the contract locking itself up, and understand when low block gas limits would start endangering the contract to be able to evaluate the severity and likelihood of such scenarios, and how large the margin of safety is.

should throw if sending zero ether tests for negative ether

it('Should throw if sending zero ether', async function() {
let errorThrown = false
let ethToSend = -2
let receiver = accounts[3]
let proxyBalance
let balance = await web3.eth.getBalanceAsync(proxy1.address)
proxyBalance = web3.fromWei(balance, 'ether').toNumber()
try {
await proxy1.forward(receiver, web3.toWei(ethToSend, 'ether'), '0x0', {from: ownerProxy1})
} catch(e) {
errorThrown = true
}
assertThrown(errorThrown, 'An error should have been thrown')
bal = await web3.eth.getBalanceAsync(proxy1.address)
assert.equal(web3.fromWei(balance, 'ether').toNumber(), proxyBalance, 'Balance of proxy should not have changed')
})

The 'Should throw if sending zero ether' test actually tests for throws when sending negative ether.

Difficulty finding testing documentation from README

Our initial review found a lack of testing documentation, without which we were forced to make inferences about what is correct and desired testing behavior. This is made difficult by the number of localhost test networks described in truffle.js. Upon discussion with the uPort team, we were directed to testing instructions within CONTRIBUTING.md,

Recommendation

We recommend a note in the README that directs the viewer to tests when linking to CONTRIBUTING.md.

Proxy tests

Since proxy.sol is the highest priority, it should have much more tests, possibly even bordering on the paranoid.

Seems there should be tests like the following, as well as mixing combinations of them:

different owners, including an external account, owner is transferred to 0, owner is transferred to address of proxy itself, external owner transferred to a contract, a contract owner transferred to an external account
differentdestination like 0, the owner itself, the proxy itself
different value, including negative, and 1 wei
proxy's balance is <, =, and > than value
different data, including length 0, 1, 2, some intermediate length, some large length
...

Front running

Miners have ultimate control over transaction ordering and inclusion of transactions on the Ethereum Blockchain. This means that miners are ultimately able to decide which transactions are filled or canceled. Given this inherent power of miners it opens up a possible form of front running.

The IdentityManager and MetaIdentityManager contracts are susceptible to front running as the order in which owners and recoveryKeys are added and/or removed is very important in the case of an identity takeover attack by a malicious owner.

e.g. In the event that a malicious owner with olderOwner status calls removeOwner() on a benevolent owner in the same block that the benevolent owner calls removeOwner() on the malicious owner, the order in which these transactions are processed are very important.

adminTimeLock helps mitigate front running by preventing a malicious owner from gaining olderOwner status for a short period of time before front running becomes a potential issue. The rateLimited() modifier helps in that it restricts the number of administrative action an older owner may take, decreasing the chances of potentially conflicting transactions in the transaction pool.

Ref: Best Practices: Transaction-Ordering Dependence (TOD) / Front Running

Recommendation

We recommend that the values of userTimeLock, adminTimeLock, and adminRate be chosen with care so as to balance utility with security.

Migrations can result in unintended owners

delete owners[identity][msg.sender];

When an identity is migrated to another controller, the owner that sends the migrate command is cleared from the contract. However, any other owners remain associated with the identity. If the identity is ever migrated back to the same IdentityManager contract, the preexisting owners will still be present.

Recommendation

Update the documentation with a warning about this case. Consider adding a registration timestamp so owners with earlier timestamps can be ignored, but this scenario might not be worth the added complexity.

RecoveryQuorum can have more than 15 delegates

addDelegate appears to allow up to 15
(in RecoveryQuorum.sol#L75)

but the constructor has no limits on how many delegates can be added initially.

The 15 should also be a constant, instead of a magic number.

Deleting a Delegate struct

delete delegates[delegateAddresses[i]]; seems like it should work here:

(in RecoveryQuorum.sol#L94)

The generated bytecode from the compiler may also save some gas.

Improper timelock values can lead to unintended behavior

IdentityManager expects adminTimeLock to be longer than userTimeLock. This is not enforced. If set incorrectly, the identity creator will not be able to send transactions immediately. Owners added from recovery will receive admin privileges first, which they can use to add themselves as an owner again and receive transaction privileges.

Recommendation

Require adminTimeLock >= userTimeLock.

Explicitly mark visibility in functions and state variables

eg Should changeUserKey be private or internal (if it's only intended to be called by signUserChange) or is the intention for it to be public?

(in RecoveryQuorum.sol#iL34)

https://github.com/ConsenSys/smart-contract-best-practices#explicitly-mark-visibility-in-functions-and-state-variables


zmitton commented 22 days ago
@ethers this one is intended to be able to be called by anyone after a signature is provided (via signUserChange), and then the time period has passed


ethers commented 14 days ago
yes, explicitly using public makes the intent clearer.
(Generally missing code is harder to detect, and so a missing private or internal could cause insecurity in other cases.)

Seemingly arbitrary `delete` statements in `IdentityManager::finalizeMigration()`

delete migrationInitiated[identity];
delete migrationNewAddress[identity];
identity.transfer(newIdManager);
delete recoveryKeys[identity];
delete owners[identity][msg.sender];

In the finalizeMigration() function of IdentityManager and MetaIdentityManager, the order and purpose of the delete statements are unclear.

Recommendation

Consider adding a rationalization of the delete statements to the documentation or as comments in the contract code to mitigate confusion.

`MetaIdentityManager::addOwner()` can be called for existing owner

function addOwner(address sender, Proxy identity, address newOwner) public
onlyAuthorized
onlyOlderOwner(identity, sender)
rateLimited(identity, sender)

As either addOwner() function is written, a malicious olderOwner could call addOwner on an existing owner of owner or olderOwner status. This function call overwrites the time that existing owner became an owner in the owners mapping, and may be used as an attack to remove an older owner's admin privileges.

Recommendation

We recommend the addition of a require statement in both functions that ensures that addOwner() can only be called on an owner that is not a current owner.

`can not remove other owner yet' tests for adding owner instead

it("can not remove other owner yet", async function () {
types = ['address', 'address', 'address']
params = [user2, proxy.address, user1]
p = await signPayload(user2, sender, txRelay, identityManager.address,
'addOwner', types, params, lw, keyFromPw)
res = await txRelay.getAddress.call(p.data)
assert.equal(res, user2, "Address is not first parameter")
tx = await txRelay.relayMetaTx(p.v, p.r, p.s, p.dest, p.data, {from: sender})
assert.isUndefined(tx.receipt.logs[0], "Generated logs, thus owner was removed")
})

The 'can not remove other owner yet' test actually tests for not being able to add another owner yet.

Multiple variables named `owner`

address public owner;

Each Proxy contract specifies its owner as the single address that may call functions modified by isOwner(), which is more often than not the IdentityManager or MetaIdentityManager.

Within the IdentityManager and MetaIdentityManager, an identity's owners are defined as the whitelisted addresses which may have transactions to that identity's Proxy contract via either identity manager.

Though similar, these variables describe different things, and are confusing.

Recommendation

We recommend the renaming of the owner variable in the Proxy contract.

`ZeroFunctionSelector` compiler bug in `solidity <0.4.18`

A very low severity solidity compiler issue in which it was possible to craft the name of a function such that it was executed instead of the fallback function in very specific circumstances was recently fixed in version 0.4.18 on October 18th.

As described in the Etherscan Soldity bug info table:

If a function has a selector consisting only of zeros, is payable and part of a contract that does not have a fallback function and at most five external functions in total, this function is called instead of the fallback function if Ether is sent to the contract without data.

Recommendation

None of these contracts seem to be affected by the ZeroFunctionSelector compiler bug. The benefits of jumping to a few week old 0.4.18 compiler do not seem to outweigh the risks of potential compiler bugs introduced in 0.4.18.

`IdentityManager::addOwner()` can be called for existing owner

function addOwner(Proxy identity, address newOwner) public onlyOlderOwner(identity) rateLimited(identity) {
owners[identity][newOwner] = now - userTimeLock;
LogOwnerAdded(identity, newOwner, msg.sender);
}

As either addOwner() function is written, a malicious olderOwner could call addOwner on an existing owner of owner or olderOwner status. This function call overwrites the time that existing owner became an owner in the owners mapping, and may be used as an attack to remove an older owner's admin privileges.

Recommendation

We recommend the addition of a require statement in both functions that ensures that addOwner() can only be called on an owner that is not a current owner.

Incorrect explanation of getAddress

TxRelay.getAddress pulls the first argument to the function to be called and interprets it as an address. The code works fine, but in a comment, it explains:

//36 is the offset of the first param of the data, if encoded properly.
//4 bytes for the function signature, and 32 for the addess.

This is incorrect. The bytes array begins with 32 bytes for the length of the array, then 4 bytes for the function signature, followed by the first argument of the function. It's worth elaborating that this only works if the function being called looks like MetaIdentityManager.forwardTo(address sender, Proxy identity, address destination, uint value, bytes data).

Recommendation

Update the comment with the correct explanation. Consider adding an explanation that the function expects something like forwardTo to be called.

Constant functions

eg looks like collectedSignatures and neededSignatures should be constant

(in RecoveryQuorum.sol#L57)


zmitton commented 22 days ago
ethers I can make this change. I'm unaware what the difference is, in terms of bytecode. I know the API from web3 is slightly different, but no one has been able to tell me the real difference


ethers commented 14 days ago
Currently, it helps web3js frontends ensure they use an eth_call and get the return value instead of a transaction hash

Usage in new libraries?

uport-identity was using an interesting implementation of Meta transactions in the TxRelay contract.
Is a similar approach also used in more up to date libraries of uPort?

Use an exact version of Solidity compiler

eg
pragma solidity 0.4.4;
instead of
pragma solidity ^0.4.4;

Helps prevent compiling and thus testing and deploying with unintended or mismatching compiler versions (like tests done with one version, and deployment is with another version).

Also, some files have a different pragma from other files and all files should use the compiler version unless there's a good reason and clear documentation and explanation.

documentation refers to nonexistent function

tr->tr:verifySignature

A diagram that explains the control flow of a meta transaction refers to a verifySignature() function does not exist in the TxRelay contract. While its purpose may be inferred from the diagram, in the interest of exactness, verification of the meta transaction actually occurs as a require statement within the relayMetaTx() function in the TxRelay contract.

Recommendation

  • update the documentation to reflect the require() statement

Protect users against known paths to "bricking" the contract system

There are many ways in which the contract system could be misused, either by user error, or as the result of social engineering.

Protecting the user against all known ways they can change their setup (with controller/quorum/userKey, etc) is not possible, and some of this protection is left to the front-end. However, there are scenarios where it is cheap in terms of gas, and obvious wrong choices that the smart SCS can enforce.

For example, changing the Proxy's owner to itself should could be prevented by slightly modifying the transfer function to:

function transfer(address _owner) onlyOwner {
	if (_owner == address(this)){
    	owner = _owner;
	}
}

The rest of the Ethereum ecosystem is unknown, but the smart contracts have relative knowledge about its own threat model, and it would be cheap to implement.

deletedAfter magic number

Needs at least a comment, and using a constant is probably better too.

deletedAfter: 31536000000000

(in recoveryQuorum)

Front Running

uint adminTimeLock;
uint userTimeLock;
uint adminRate;

Miners have ultimate control over transaction ordering and inclusion of transactions on the Ethereum Blockchain. This means that miners are ultimately able to decide which transactions are filled or canceled. Given this inherent power of miners it opens up a possible form of front running.

The IdentityManager and MetaIdentityManager contracts are susceptible to front running as the order in which owners and recoveryKeys are added and/or removed is very important in the case of an identity takeover attack by a malicious owner.

e.g. In the event that a malicious owner with olderOwner status calls removeOwner() on a benevolent owner in the same block that the benevolent owner calls removeOwner() on the malicious owner, the order in which these transactions are processed are very important.

adminTimeLock helps mitigate front running by preventing a malicious owner from gaining olderOwner status for a short period of time before front running becomes a potential issue. The rateLimited() modifier helps in that it restricts the number of administrative action an older owner may take, decreasing the chances of potentially conflicting transactions in the transaction pool.

Ref: Best Practices: Transaction-Ordering Dependence (TOD) / Front Running

Recommendation

We recommend that the values of userTimeLock, adminTimeLock, and adminRate be chosen with care so as to balance utility with security.

timeLocks and pendingUntil

This is to raise awareness that the timeLocks (longTimeLock and shortTimeLock) are "configurable" and can cause overflow with pendingUntils:

  • RecoverableController.sol#L41
  • RecoverableController.sol#L53
  • RecoverableController.sol#L65

The impact of an overflow here is that a "signed key change" could be made permanent (does not expire). This is to raise awareness because I don't see any immediate dangers, and while contrived here's the start of a possible scenario:

users create their identities using some identity factory by going to a uPort website
if the uPort website is compromised silently, it could be made to invoke the factories with timeLocks that would cause overflow
user identities are created without the "signed key change" expirations/protections that were designed in the RecoveryController
EDIT: Also, overflowing with timeLocks can also affect the instant add and removal of delegates in RecoveryQuorum
RecoveryQuorum.sol#L76

`IdentityManager::removeOwner()` and `MetaIdentityManager::removeOwner()` may be called on oneself

/// @dev Allows an owner to remove another owner instantly
function removeOwner(Proxy identity, address owner) public onlyOlderOwner(identity) rateLimited(identity) {
delete owners[identity][owner];
LogOwnerRemoved(identity, owner, msg.sender);
}

As either removeOwner() function is written, an older owner has the ability to remove oneself as an owner. This functionality increases the likelihood that an owner accidentally deletes itself.

Recommendation

Add require() statement in both functions that ensures that removeOwner() cannot be called on oneself.

Use `keccak256` instead of `sha3`

bytes32 h = sha3(this, nonce[claimedSender], destination, data);

In Solidity, sha3 and keccak256 are aliases for the same function, with keccak256 being the more accurate name which does not mislead new or casual observers about which hash algorithm is being executed (since the standardized SHA-3 is slightly different from Keccak-256).

Recommendation

Use keccak256 instead of sha3.

ERC-725 adoption

Is the uPort team considering adopting / supporting the ERC-725 Identity standard?

The onlyOwner() modifier does not throw

The typical onlyOwner() modifier throws if called by someone other than the owner. Throwing seems to be a slightly more conservative approach. We recognize that there may be reasons related to gas usage for not throwing.

Recommendation

Update access control logic to throw when called by an unauthorized party, or provide comments clarifying the motivation the existing pattern.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.