arc
arc copied to clipboard
Generalize access control mechanism
I've created a PoC for a more general access control mechanism for Arc, this file contains a PoC with a simple usage example. The basic idea is that there are a set of "locks"/"topics" each with a set of "keys". A contract can protects one of it's methods with a lock (or more) such that anyone with the appropriate key can use it to access this method. Each key can have an expiration time, # of uses. Accounts can also transfer some (or all) of their capabilities to other accounts if allowed. Initially the contract itself has a "master key" (infinite uses, no expiration), and then it can pass some partial capabilities to the desired accounts.
Features:
- Can define a number of uses & expiration date on keys and if to enable the key to be transfrred.
- Accounts can transfer partial (or full) abillites to other accounts (if allowed).
- We can use advanced lock ids to lock a method even on specific parameters, enforce ordering, or other properties (see example).
- Can define complex boolean predicates to protect methods.
- No performance loss over hand-crafted mechanisms.
Benefits:
- Declarative policy (no more sprinkling
ifs all over the codebase) - Easy control flow (ordering, timing, number of function calls) restrictions (e.g. only allowed to call
Bafter callingAtwice within 2 days). - Allows accounts to call functions themselves instead of delegating through other contracts.
- Generalized logging mechanism: key usage events can be queried by clients to know whether a method was called and by which account, key revocations can be used to know when users no longer have access to a method.
- Easier security since we need to verify less code.
@dev-matan-tsuberi Can you provide a list of use cases (like user stories), and one or more examples in code that demonstrate some of the use cases?
@dkent600 I've included a simple usage example in the linked file Re: use cases, it's very general, it can be anything really:
- Our current permission system in
Controller. - We can imagine a DAO that has different types (roles) of agents. Different roles may have different capabilities.
- A helpful utility when developing any kind of contract that cares about enforcing the order, timing or number of calls to it's methods (that's basically any contract).
- We can have a marketplace of such permissions with people exchanging keys for various functions.
This system is really just a very general access control system for a contract's functions. The contract can be whatever and the logic can be very complex or very simple.
Hi @dev-matan-tsuberi ,
Just had time to review this. I like it very much. How about we try to integrate it in Arc?
Maybe @dev-matan-tsuberi and @ben-kaufman together?
@leviadam Thanks! Sure we can integrate it. Of course this is currently only a POC, so it's not perfect. And there's a little concern to think about (decide if it's tolerable or invent a solution).
@ben-kaufman What do you think?
I know it is not perfect. This will require some research and work. We have to consider gas costs. And also solve the problem you mentioned.
We should also have an EIP, and ask for comments. But I think we should go down this road :)
@leviadam Why EIP?
I think it is a useful concept, and also people building schemes should be aware of it. It is good to try to standardize it.
I like the implementation, looks really interesting and useful. I agree with @leviadam an EIP for this can be good as it's very generalized and I think it can be very good to have that as a standard. I'll go deeper throw the code in the next few days and suggest changes where needed.
@leviadam Re: gas costs, I think gas costs are not any different from a hand-crafted solution since its not storing anything more or doing any more logic than absolutely required. i.e. if you want to restrict the no. of uses you'd have to at very least maintain a counter, if you want to restrict to a specific time you'd have to remember that time, etc..
Potential improvements:
- Have a magic number that allows client code to check if a contract is
Protected. - Somehow allowing accounts to specify which key they want to use if there are multiple options. Currently it works by taking the first key that fits the lock.
- Instead of each
Protectedcontract maintaining his own locks & keys, have one global contract as-a-service that manages all locks & keys, allowing contracts to share locks and interact in more complex ways. - Having the concept of a "Group" of accounts that can be assigned/revoked permissions all at once. This can be simply implemented by having a proxy contract that maintains a set of addresses belonging to the group, permissions are given to the proxy contract itself and members of the group can forward requests through it to the target contract.
Regarding the first comment, I think we should implement EIP165 which specifies how to do that.
I'm currently trying to solve the issue you mentioned, will update when I'm finished.
open-zeppelin has kind of solution for access control . See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/access/SignatureBouncer.sol
@ben-kaufman EIP165 is cool, didn't know about that, are there other similar standards? However, regarding the issue, how are you intending to solve it? I intentionally didn't try to solve it directly because I did not find a solution which doesn't introduce some additional computational complexity over hand crafted solutions.
@leviadam can we have a daostack repo for this?
@dev-matan-tsuberi I am still trying to figure this out, currently my thought here is to use an array on Keys instead of a single key so you might have a few keys for the same id but with different properties (keys with the same property are still grouped together with the uses). I'm just concerned here about the gas costs. Did you try that previously? If not what do you think about doing so?
@dev-matan-tsuberi I get multiple compile errors on the original code. Some small things but there's a problem with passing a dynamic sized array as the modifier parameter. It can be easily solved by saving the constant as a global variable but the problem is hashing it with another non static value (like the parameters of a protected function. Have you noticed that before?
@ben-kaufman
I don't fully understand your idea, but this is more complicated than it seems. The naive way will need to keep a list of uses sorted by expiration, checking if a key is valid can take O(log(n)) to search the list. Merging keys is O(max(n,m)).
So unless we do some clever algorithmic tricks, it doesn't seem feasible. And it complicates the code quite a bit. I'm willing to grant that transfer takes more than O(1), but the only modifier absolutely needs to be O(1) worst-case.
@dev-matan-tsuberi But you will still have problem with the transferable permission.
What I meant is that instead of having one Key per id we can have array of Keys. Each key struct will represent properties of the transferable and expiration time. Is that more clear? This way it's also easier for client to specify which key to use for the id and thus can save gas by letting the client do all array iteration work and just verifying in the contract that the given index is legit.