lukso-network/LIPs

LSP6 key permissions

frozeman opened this issue · 9 comments

We could add a new ERC725Y schema to allow adding keys and their permissions to a ERC725Y key value store:

web3.utils.keccak256('keys')
'0xf29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195'
0xf29790a8 0000000000000000
key:
0xf29790a8 0000000000000000 + address

value:
- keytype: key manager, execution key, execution key - value, execution key - can change store
- contracts to interact with, or all
- functions to call on each contract, or all

0xffffffff + contractaddress + array
^ keytype

permissions:
{
contracts: {
    contract1: [function, function],
    contract2: [function, function]
}
}

KEY: keypermissionSignatureKey + [toAddress] + fromAddress
VALUE: keyType + purpose + functionSignaturesArray

keyType = ECDSA, RSA
purpose = execution, key management, deploy contract, move value
functionSignaturesArray = should allow also NOT to call a funcSig (!0xff12345?)

2 calls:

  1. call for generic allowance (toAddress = 0000000000000000)
  2. IF empty: call for specific allowance (toAddress = 0xaddress)
    (or reverse?)
// ERC725AccountKeyRoles:Roles:address

web3.utils.keccak256('ERC725AccountKeyRoles')
"0xd76bc04c245897e9724b7cce565388aef586d45d119cb975e0279342b5215255"

web3.utils.keccak256('Roles')
"0xeced5608e693513b395adb7c3f4a39de5b01119807e6c0c256183c9dc50d5491"

address

'0xd76bc04c00000000' + 'eced0000' + address

4 + 4 + 2 + 2 + 20 

VALUE:
bytes4
// bitmask
    bytes1 internal constant ROLE_CHANGEKEYS = 0x01; ???
    bytes1 internal constant ROLE_SETDATA = 0x01;
    bytes1 internal constant ROLE_EXECUTE = 0x01;
    bytes1 internal constant ROLE_TRANSFERVALUE = 0x01;
    bytes1 internal constant ROLE_SIGN = 0x01;
// ERC725AccountKeyRoles:AllowedAddresses:address

web3.utils.keccak256('ERC725AccountKeyRoles')
"0xd76bc04c245897e9724b7cce565388aef586d45d119cb975e0279342b5215255"

web3.utils.keccak256('AllowedAddresses')
"0xc6dd6b3ce58ef989d54946d53fb4e5cb12faebf76b6bf089e73406cd381a4a15"

address

'0xd76bc04c00000000' + 'c6dd0000' + address

VALUE:
address[]
// ERC725AccountKeyRoles:AllowedFunctions:address

web3.utils.keccak256('ERC725AccountKeyRoles')
"0xd76bc04c245897e9724b7cce565388aef586d45d119cb975e0279342b5215255"

web3.utils.keccak256('AllowedFunctions')
"0x8efea1e8ec4e1dc8ef33d2cfb15ed2003776f612f78a9547e200ea876a7f7597"

address

'0xd76bc04c00000000' + '8efe0000' + address

VALUE:
bytes4[]
// ERC725AccountKeyRoles:AllowedStandards:address

web3.utils.keccak256('ERC725AccountKeyRoles')
"0xd76bc04c245897e9724b7cce565388aef586d45d119cb975e0279342b5215255"

web3.utils.keccak256('AllowedStandards')
"0x3efa94a37c44afdbd8c9830e930000eacdd80ab7dc5ee3d687d8df7a0f8ddd0e"

address

'0xd76bc04c00000000' + '3efa0000' + address

VALUE:
bytes4[]

@leondroids Better change ERC725AccountKeyRoles:Roles:address to AddressPermissions:Permissions:<address>
And all other keys as well to AddressPermissions:AllowedStandards:<address>.

The reasons being, that address permissions can be also on non ERC725account contracts, and the word Key is misleading, as it can be taken as an ERC725Y key.

CJ42 commented

I am writing my idea to support and extend on this LSP6 Proposal. It describes:

  1. how to verify permissions stored under AddressPermissions:AllowedStandards:<address>, and
  2. how the KeyManager can give meaningful result if a permission check fails.

This extends on the above comments by @frozeman, to introduce 3 different types of results when checking permissions:

  • PASS
  • FAIL
  • REFER

What is a permission in LSP6?

A permission is represented as a single bit set in a bytes1 value. Below are standards examples:

CHANGEOWNER   =  0x01  // 0000 0001
CHANGEKEYS    =  0x02  // 0000 0010
SETDATA       =  0x04  // 0000 0100
CALL          =  0x08  // 0000 1000
DELEGATECALL  =  0x10  // 0001 0000
DEPLOY        =  0x20  // 0010 0000
TRANSFERVALUE =  0x40  // 0100 0000
SIGN          =  0x80  // 1000 0000

If a user / application's address is granted multiple permissions, its overall permission level corresponds to the sum of all the bytes value of each permissions.

The example below shows the overall permission level for an address (eg: 0xcafecafecafecafecafecafecafecafecafecafe) that has been authorized to do CALLs, TRANSFER VALUE or SIGN on behalf of the ERC725 Account.
This value would be stored in the ERC725 Account under the key AddressPermissions:AllowedStandards:<address>, as defined above.

Example of User Permissions Stored:

  SETDATA.      =  0x04  // 0000 0100
+ CALL          =  0x08  // 0000 1000
+ TRANSFERVALUE =  0x40  // 0100 0000
+ SIGN          =  0x80  // 1000 0000
--------------------------------------
 TOTAL          =  0xCC  // 1100 1100

When a user performs an action on behalf of the ERC725 Account, a KeyManager (as introduced by @frozeman) must verify that the user (=caller) has the minimum permission(s) required.

Example

Let's illustrate with an example. Alice wants Bob to sign a document for her, on her behalf, via a digital notary service.
Bob must have the following permissions granted to perform Alice request.

1. Interact with the smart contract of the notary service (permission required = 0x08)
2. Pay the notary in ether for the service provided (permission required = 0x40)
3. Sign a document created by the notary service (permission required = 0x80)

Example of Permissions to verify

  CALL          =  0x08  // 0000 1000
+ TRANSFERVALUE =  0x40  // 0100 0000
+ SIGN          =  0x80  // 1000 0000
--------------------------------------
 TOTAL          =  0xC8  // 1100 1000

Formula

To verify a permission, the KeyManager must perform a bitwise operation, by ANDing the permissionsRequired (= actions being performed), against the permissionsStored of the user performing the actions (= user's permissions stored under ERC725Y).
So result = permissionsRequired & permissionsStored

  permissionsRequired        =  0xC8  // 1100 1000
& permissionsStored          =  0xCC  // 1100 1100
---------------------------------------------------
= result

Potentials Results

Performing a bitwise AND returns 1 if both bits being compared are 1. Otherwise if one of the bit is 0, ANDing will return 0.

This operation can return 3 potential results, that can then lead to the following interpretations:

Result 1: user has all the permissions required

The user has all the permissions required to perform the action if the result of the AND operation returns the permission being checked.

  permissionsRequired        =  0xC8  // 1100 1000
& permissionsStored          =  0xCC  // 1100 1100
--------------------------------------------------
  result.                    =  0xC8  // 1100 1000

In this case, Bob has been granted these permissions previously. So he can perform Alice's request successfully.

Result 2: user has none of the permissions required

The user has none of the permissions required to perform the action if the result of the AND operation return 0.

  permissionsRequired        =  0xC8  // 1100 1000
& permissionsStored          =  0x04  // 0000 0100
--------------------------------------------------
  result.                    =  0x00  // 0000 0000

Carol has been previously granted permission only to SETDATA on Alice's ERC725 Account. Therefore, his overall permission is 0x04 = 0000 0100.

As shown above, ANDing Carol's permission level against the permissionRequired returns 0.
This indicates that the operation will fail, and Carol is not allowed to perform any of the actions requested.

Result 3: user has part of the permissions required

The user has part of the permissions, but not all of them required to perform the action if the result of the AND operation return any other number than 0 or the permission being checked.

  permissionsRequired        =  0xC8  // 1100 1000
& permissionsStored          =  0x98  // 1001 1000
--------------------------------------------------
  result.                    =  0x88  // 1000 1000

Alice has previously subscribed to a new social media application, built by Dave. The app is powered by a smart contract on Ethereum.

In order to use the app's features, Alice has previously granted Dave's app the following permissions (so that the app can interact with its ERC725 Account).

Total App permissions (granted by Alice)

  CALL          =  0x08  // 0000 1000
+ DELEGATECALL  =  0x10  // 0001 0000
+ SIGN          =  0x80  // 1000 0000
--------------------------------------
 TOTAL          =  0x98  // 1001 1000

In this case, we could think that the app has some of the permissions necessary to interact with the notary contract and sign the document. However, the app will not be allowed to transfer ethers to pay the notary. Therefore, it cannot complete the operation in full and the interaction should revert.

We have seen before that ANDing returns 1 if both bits compared are set. It returns 0 if:

  • none of the bits compared are set (= so both are 0)
  • at least one of the bit is set (= one is 0, the other is 1)

Dave's app has the permissions to CALL and SIGN. These being required, ANDing them return 1, as they are set on both side.

However, it does not have the permission to TRANSFERETHER. So the ANDing will return 0.

(The app has also the permission DELEGATECALL, which is not required for the action to be performed. I have added it here for better comparison between the permissions, and to show the result of the bitwise AND).

Summary

Since running a bitwise AND against the permissions can result in 3 different types of results, we can then interpret these results into 3 different categories.

High Level Result Result Description
Pass permissionRequired Authorised. Has all of the permissions necessary.
Fail 0 Not authorised. Has none of the permissions required.
Refer any other number Has partially some of the permissions required, but not all.

NB: although a ❓ Refer means the caller owns part of the permissions required, it should still fail and not allow the caller to perform the action.

Finding missing permissions

Let's go back to our result 3, where part of the permissions where owned, but not all. The missing permission via bitwise XOR (^ or Exclusive-OR).

XOR returns 1 if only one of the expression has the value as 1.
See examples below:

         0       1       1       0
^        0       1       0       1   
----------------------------------- 
result = 0       0       1       1

It is possible to find the missing permission(s) required by XORing the result of the previous Bitwise AND (= ❓the Refer result) against the permissionRequired.

The example below returns 0x40, which correspond to TRANSFERVALUE, the permission missing by Dave's app.

 ❓ Refer result        =  0x88  // 1000 1000
^ permissionsRequired   =  0xC8  // 1100 1000
----------------------------------------------
  result                =  0x40  // 0100 0000 = TRANSFERVALUE
CJ42 commented

Current draft: https://github.com/CJ42/LIPs/blob/lsp6-keymanager/LSPs/LSP-6-KeyManager.md

@frozeman the link to the old draft does not work.

This is the link to the original first draft: https://github.com/CJ42/LIPs/blob/b4c1adbc4852d3421a528d5860608dabf0b84989/LSPs/LSP-6-KeyManager.md

This issue can be closed.
The standard has been implemented in PR #15