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 delegate & authorizer feature #31

Closed
0xjac opened this issue Feb 12, 2018 · 1 comment
Closed

Add delegate & authorizer feature #31

0xjac opened this issue Feb 12, 2018 · 1 comment
Assignees

Comments

@0xjac
Copy link
Owner

0xjac commented Feb 12, 2018

Delegate & Authorizer

A delegate is an address which has been authorized to send the tokens of a principal address. When calling send on the token contract from the delegate, the tokens will be sent from the principal account not the delegate. In addition, each send involving a delegate will be explicitly authorized by an authorizer contract.

Delegate/Authorizer vs Operator

There is a key difference between an operator and a delegate.

To send tokens with an operator, the operator must call operatorSend on the token contract. This should either be done automatically as a result of some action or by calling a function directly on the operator.

To send tokens with a delegate, the delegate only needs to call send on the token contract. The token contract will figure out on his own that send was called from a delegate and first call the authorizer contract for authorization, then send from the principal address being represented by the delegate. This allows for checks to be used with wallets which just implement send.

Delegate/Authorizer vs tokensToSend

tokensToSend is registered via EIP-820 together with tokensReceived. This means that the same function is called for all ERC-777 tokens. This is fine for tokensReceived to notify the recipient and prevent locking because the logic is the same for all tokens.

This is not true for tokensToSend which is aimed at checking whether a send is authorized or not. In this case the logic may not be the same for all tokens. Maybe a specific tokens should be rate-limited, an other one should be limited to be sent to a particular address and so on. Because there is only a single tokensToSend function for all tokens, this function must be modified and the contract redeployed for every new tokens which must be handled.

Authorizers on the other hand are registered directly on the token contract and are only applied for the specific token. This keeps the logic simple and prevent constant modification and redeployment of the contract.

Another advantage of delegates is that they use a separate account than the principal account. In the case of tokensToSend, since it is deployed for the account via EIP-820, the same key used to send tokens can also unregister the tokensToSend, essentially making the check a self-imposed voluntary check which can be bypassed at any time.

Delegate is a separate address, tied to an authorizer; but only the principal account key has the power to change the authorizer. This means that the delegate can never bypass the checks.

Use Cases

Debit card

Consider a principal account holding a large amount of tokens. The owner of the account must carry the private key with him at all time to use his tokens which may be dangerous.

A delegate with a daily quota could be authorized to send some of the tokens. The owner could carry this delegate on his phone and use his phone for limited transactions on a daily basis, the same way a debit card works. And when the owner needs to do an occasional large transaction, he can use the principal private key which is stored securely somewhere less practical to access (for example on a hardware wallet in a safe.).

Cold to hot wallet transfer

Consider a cold wallet holding a large amount of tokens and a second hot wallet used for daily transactions.

The cold wallet can have a delegate which is only authorized to send to the hot wallet. The key of the cold wallet is stored securely and almost never used. The delegate is used to replenish the hot wallet from the cold wallet.

In addition backups of the delegate key can be given to trusted entities for safe keeping. This way even if the cold wallet key is lost, the delegate key can be used to recover the funds.

And if the delegate key is compromised the only thing an attacker can do is send the tokens from the cold to the hot wallet. This is a bit safer than entrusting the key of the cold wallet directly.

Specifications

IAuthorizer

The authorizer must be a contract implementing the following interface:

interface IAuthorizer {
  function authorizeSend(address to, uint256 value, bytes userData) public;
}
authorize
function authorizeSend(address to, uint256 value, bytes userData) public

Authorizes a send for the from address being represented by the delegate.

To prevent the send, the function MUST throw.

parameters

  • to: tokens recipient
  • value: amount of tokens transferred
  • userData: information attached to the transaction by the sender

Ierc777 (Token Contract)

In addition, the Ierc777 interface must be extended to add the following methods and events:

Methods

function authorizeDelegate(address delegate, address authorizer) public;
function revokeDelegate(address delegate) public;
function delegateFor(address delegate) public constant returns (address);
function authorizerOf(address delegate) public constant returns (address);

event AuthorizedDelegate(address indexed delegate, address indexed authorizer, address indexed tokenHolder);
event RevokedDelegate(address indexed delegate, address indexed authorizer, address indexed tokenHolder);
authorizeDelegate
function authorizeDelegate(address delegate, address authorizer) public

Authorize a third party delegate to send msg.sender's tokens upon authorization of authorizer.

The function MUST throw if:

  • delegate is msg.sender
  • delegate is already a delegate for another principal
  • delegate holds tokens (non-zero balance)
  • authorizer is an operator
  • authorizer is already an authorizer for some delegate

NOTE: While authorizer MUST be a contract, there is no requirement to check it when authorizing the delegate and authorizer. When sending tokens however the authorizeSend function of the authorizer MUST be called, which will result in a throw if authorizer is not a contract or does not implement Authorizer.

parameters

  • delegate: Address that will to be authorized to send msg.sender's tokens.
  • authorizer: Contract which will authorize _delegate to send tokens.

revokeDelegate
function revokeDelegate(address delegate) public

Revoke a third party _delegate's rights to send msg.sender's tokens.

The function MUST throw if:

  • delegate is not the delegate of msg.sender.

NOTE: While the authorizer does not need to be specified when revoking, internally the authorizer MUST also be revoked (i.e. not marked as the authorizer for delegate ).

parameters

  • delegate: Address of the delegate for msg.sender.

TODO Improvement: Since, there can be only one delegate per account, a two-way mapping of the delegate could be kept such that msg.sender does not need to specify the delegate when revoking


delegateFor
function delegateFor(address delegate) public constant returns (address)

Get the address for which delegate is authorized to send tokens or 0x0 if delegate is not a delegate.

parameters

  • delegate: Address for which to return the principal address.

returns: Address for which _delegate is authorized to send tokens or 0x0 if _delegate is not a delegate.


authorizerOf
function authorizerOf(address _delegate) public constant returns (address)

Get the address for which delegate is authorized to send tokens or 0x0 if delegate is not a delegate.

parameters

  • delegate: Delegate address for which to return the authorizer contract address.

returns: Authorizer contract address of _delegate or 0x0 if _delegate is not a delegate.


Events

AuthorizedDelegate
event AuthorizedDelegate(address indexed delegate, address indexed authorizer, address indexed tokenHolder)

Indicate that the delegate address is representing tokenHolder and send its token (i.e. is a delegate for tokenHolder).

This event MUST be fired on a successful call to authorizeDelegate.

parameters

  • delegate: Address which is now representing tokenHolder.
  • authorizer: Contract which will authorize _delegate to send tokens.
  • tokenHolder: address being represented by delegate.

RevokedDelegate
event RevokedDelegate(address indexed delegate, address indexed authorizer, address indexed tokenHolder)

Indicate that the delegate address is not representing tokenHolder anymore and cannot send its tokens (i.e. is not delegate for tokenHolder).

This event MUST be fired on a successful call to revokeDelegate.

parameters

  • delegate: Address which is not representing tokenHolder anymore.
  • authorizer: Contract which will authorize _delegate to send tokens.
  • tokenHolder: address not being represented by delegate anymore.


Furthermore the send', 'operatorSend', 'transfer' and 'transferFrom method specification as well as the Burned event specification must be extended with the following constraint:

If from is a delegate, then from.authorize MUST be called and pass the correct values for to, value and userData.

NOTE: While intuitively the authorize call happens before sending the tokens, in the implementation, this call SHOULD be after the update of balances and before the call to tokensReceived to respect the Checks-Effects-Interactions (CEI) pattern and prevent potential reentry attacks.

When from is a delegate, the tokensReceived, Sent and Burnt must be called with the true from (i.e. the token holder being represented by the delegate) not the delegate itself. Delegates are transparent from the point of view of the recipient and MUST NOT be communicated.

@0xjac 0xjac changed the title Add delegates Add delegate & authorizer feature Feb 13, 2018
0xjac pushed a commit that referenced this issue Feb 13, 2018
0xjac pushed a commit that referenced this issue Feb 13, 2018
@0xjac 0xjac self-assigned this Feb 13, 2018
0xjac pushed a commit that referenced this issue Feb 13, 2018
@0xjac
Copy link
Owner Author

0xjac commented Feb 23, 2018

Closed in favor of #23 & #25 which provides similar functionalities in a less convoluted way.

@0xjac 0xjac closed this as completed Feb 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant