Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP-1654: Off-chain dapp-wallet authentication process #2119

Closed
wants to merge 8 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions EIPS/eip-1654.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
eip: 1654
title: Off-chain dapp-wallet authentication process with contract wallets support
author: Maor Zamski (@pazams), Christopher Scott (@chrisaxiom)
discussions-to: https://github.com/ethereum/EIPs/issues/1654
status: Draft
type: Meta
created: 2018-12-12
requires: 191
---

## Simple Summary
An off-chain dapp-wallet authentication process. The process asserts whether an entity has authorized control (informally, "ownership") over a public Ethereum address using `eth_sign`. Supports both external wallets and contract wallets.

## Definitions
- `contract wallet` A contract [account](https://github.com/ethereum/wiki/wiki/White-Paper#ethereum-accounts) deployed with the intent to be used as the ownership address for on-chain assets (including ether, ERC-20 tokens, and ERC-721 NFTs). It has the ability to transfer ether or dynamically execute actions on other contracts (acting as the owner of assets controlled by those contracts). Common examples of contract wallets are `multisig wallets` (such as the ones provided by [Gnosis](https://github.com/Gnosis/MultiSigWallet) and [Parity](https://github.com/ConsenSys/MultiSigWallet)) and `identity contracts`, as defined in [ERC-725](https://github.com/ethereum/EIPs/issues/725).
- `external wallet` An externally owned [account](https://github.com/ethereum/wiki/wiki/White-Paper#ethereum-accounts), controlled by a private key. Currently, most on-chain assets are owned by such accounts. A common example for an external wallet are the wallets generated by MetaMask.
- `authorized signer` An entity is considered to have authorized control over a wallet if either:
- It produced a signature of which the recovered address matches the wallet address.
- It produced a signature of which the contract at the wallet address responds with the magic value `0x1626ba7e` to the `isValidSignature()` contract call.

## Abstract
The authentication process starts with the dapp client component requesting a message signature from the wallet.
The client then proceeds to send the result to the dapp backend component along with the requested address to be used for authentication. The dapp backend recovers a public key from the signature, and checks if it has authorized control over the requested address. This check is done under consideration that the address may represent either an external wallet or a contract wallet. This process works with external wallets, as well with contract wallets that support the `isValidSignature()` method specified in this document.

## Motivation
Dapps frequently offer a customised off-chain user experience in addition to their smart-contract interface. For example, a dapp may provide a push notification feature to their users, allowing them to stay notified about successful state changes associated with their public addresses. For these type of features, a dapp needs a way to assert that a user has authorized control over the public address associated with their account.

A common practice dapps use in an authentication process is to only check if a recovered public key matches the requested authentication address. For contract wallets, this check is broken, as there is no corresponding private key to which to generate a signed message, and hence why some dapps are inaccessible for contract wallet users. It is therefore argued that a more broader approach is needed.

## Specification

### Dapp

On the dapp side, the dapp-wallet authentication process MUST follow these steps:
1. Dapp client requests the wallet software to sign a challenge message via [`eth_sign`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign).
2. Dapp client sends the signature to the dapp's backend component, along with the wallet address to be authenticated with. The address may be obtained via [`eth_accounts`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_accounts).
3. Dapp backend recovers a public key from the signature.
4. Dapp backend checks if the recovered key has authorized control over the provided wallet address under the assumption it could represent an external wallet OR a contract wallet. For the case of a contract wallet, it MUST call `isValidSignature()` and expect the value `0x1626ba7e` to determine whether the entity who signed the challenge has authorized control over the wallet.
5. The result of the authorized control check is returned as the result of the authentication and the flow is complete.

A challenge message SHOULD contain a random component. This will reduce the risk of replay attacks.

A challenge message SHOULD be generated by the dapp backend AND not get sent back as input from the dapp client, but be persisted in the backend for at least the entirety of the authentication process. This will remove the risk of accepting forged challenges.

The following algorithm MAY be used by dapp backend for authenticating users with personal signed messages:

```
FUNCTION IsAuthorizedSigner(challengeString, signature, walletAddress) RETURNS (success)

SET personalChallengeHash to the hash of: challengeString prepended with `"\x19Ethereum Signed Message:\n" + len(challengeString)`

SET recoveredKey to the public key recovered from signature and personalChallengeHash

SET recoveredAddress to the address corresponding with recoveredKey

// try external wallet
IF walletAddress EQUALS recoveredAddress
RETURN true
END IF

SET challengeHash to the hash of: challengeString . We send just a regular Keccak256 hash, which then the smart contract hashes ontop to an erc191 hash.

SET contractResult to the result of calling isValidSignature(challengeHash, signature) on the contract at walletAddress

IF contractResult EQUALS 0x1626ba7e
RETURN true
ELSE
RETURN false
END IF

END FUNCTION
```

### Wallet

#### External wallet
Any software agents managing external wallets are not required to make any changes to continue to work with this process.

#### Contract wallet

##### Contract

The contract MUST implement the following method:

```Solidity
pragma solidity ^0.5.0;

contract ERC1654 {

// bytes4(keccak256("isValidSignature(bytes32,bytes)")
bytes4 constant internal MAGICVALUE = 0x1626ba7e;

/**
* @param hash contains 32 bytes of data to be verified with the signature over this contract's address.
* @param signature byte array associated with hash. MAY contain multiple concatenated signatures in case of a multi-sig wallet.
*
* Before recovering a public key, the `bytes32 hash` parameter MUST get hashed again with [EIP-191](https://eips.ethereum.org/EIPS/eip-191), with 0 for "version" and the wallet address for "version specific data".
* The function MUST return the bytes4 `0x1626ba7e` value if the public key (or keys) recovered from the signature (or signatures) is determined to have ownership over the wallet according to the wallet's own key management logic. Otherwise the function MUST return `0x00000000`.
*/
function isValidSignature(
bytes32 hash,
bytes memory signature)
public
view
returns (bytes4 magicValue);
}
```

##### User agent

A user agent intended to work with the contract MUST generate signatures over a EIP-191 hash of a regular Keccak256 hash of the challenge message.

## Rationale
EIP-1271 has done a great work with starting the discussion on a standard signature validation method for contracts. At the time of writing, it is still in draft, with several suggestions for the shape of the interface (e.g see [here](https://github.com/ethereum/EIPs/issues/1271#issuecomment-455356404) and [here](https://github.com/ethereum/EIPs/issues/1271#issuecomment-488648761)). This proposal takes one of the variations mentioned in the discussion, and builds on top of it a process for dapp-wallet authentication.

## Backwards Compatibility
- External wallets are backwards compatible with this process.
- Contract wallets need to support `isValidSignature()` as specified in this document.

## Implementation
Packages implementing the purposed algorithm:
- Javascript: https://github.com/dapperlabs/dappauth.js
- Go: https://github.com/dapperlabs/dappauth


## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).

---
Thanks to @dete @Arachnid @igorbarbashin @turbolent @jordanschalm @hwrdtm for feedback and suggestions