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:
- call for generic allowance (toAddress =
0000000000000000
) - 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.
I am writing my idea to support and extend on this LSP6 Proposal. It describes:
- how to verify permissions stored under
AddressPermissions:AllowedStandards:<address>
, and - 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 CALL
s, 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, AND
ing 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, AND
ing 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 AND
ing 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, AND
ing them return 1, as they are set on both side.
However, it does not have the permission to TRANSFERETHER
. So the AND
ing 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 XOR
ing 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
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