--- tags: identity, specification --- # Managing smart-contract roles in Switchboard In EW-DOS, all the roles are managed in Switchboard. The role definitions are stored in ENS and the claims for role assignments in DID Documents. In order to be able to use the same roles in smart-contracts, we need to devise a process to create a mapping between ENS roles, the issued claims and the addresses associated with the DID of the user. The same mechanism should be used to manage roles in smart-contracts. This document describes a proposed method to issue smart-contract compliant roles at the same time as the claims in the Verificable Credentials format. ## Entities All the entities in the diagram, except the DID, are represented as namespaces under the `IAM.EWC` name. When a role is granted to a user, a claim for the corresponding namespace is issued to the DID and the claim is added as a service endpoint to the DID document. The key used to sign the self issued claim sent to request the issuance of the role, is not restricted to `Secp256r1`. Instead, any key which has been added to the DID Document can be used. Service endpoints can be stored on-chain but they don’t have to and hence can not be used in a smart-contract. On top of that, the signatures and proofs can not be verified in a smart-contract because the signed payload is a JSON which is difficult to decode in Solidity. ```mermaid graph LR; Organisation --> Role Organisation --> Application Application --> Role Role --> Role Role --> DID ``` ## Process for Role issuance ```mermaid sequenceDiagram participant usr as User participant role as Role Definition participant swb as Switchboard participant iss as Issuer usr ->> role: get attributes role -->> usr: request structure usr ->> usr: create self issued claim usr ->> swb: issuance request swb ->> role: get issuer list role -->> swb: issuer list swb ->> iss: forward issuance request iss -->> swb: issued claim swb -->> usr: issued claim usr ->> usr: insert claim in DID document ``` This simplified process shows how a user gets a role in the form of a verifiable credential and adds it to their DID-Document. ### Process amendments The only step which requires change is the response from the issuer. In addition of sending a verifiable credential, the issuer sends a signed message which can be interpreted by a smart contract. The requirements for this additional object are: * It must be signed by a Secp256r1 key * The signed message is in the form of (address user, bytes32 role) where the role is the Keccak256 hash of the role’s ENS name. * In order to make sure that the message is correctly formed, we should use [ERC-712](https://eips.ethereum.org/EIPS/eip-712) * The issuer provides the proof that they are authorised to issue the permission. The proof must be provided in a recursive manner until the root permission #### No automatic registration in a smart contract The reason the issuer has to provide signed proof of themselves having been granted the role to issue the claim instead of adding every issuer to a registry, is that only the identities which have to interact with smart contracts should be registered on chain. Registering an identity in a public smart-contract and labeling this identity as a member of a role is a security risk. It allows an attacker to better identify the surface of attack. Hence, the number of identities registered on chain must be minimized. #### Loss in functionality The claim which is stored in the DID-Document service endpoint, contains more information than: > This user can act as this role Instead the claim must contain all the attributes which have been submitted in the issuance request. This is why the claims must be private, which makes it even harder to verify them on-chain. #### Example Given the following role structure ```mermaid graph LR; Authority --> DSO DSO --> Installer Installer --> Prosumer ``` With the following Role definitions ```json= "Authority.roles.flex.apps.apg.iam.ewc" = { issuers: { dids: ["address0"], roles: "" } } "DSO.roles.flex.apps.apg.iam.ewc" = { issuers: { dids: [], role: "Authority" } "Installer.roles.flex.apps.apg.iam.ewc" = { issuers: { dids: [], role: "DSO" } "Prosumer.roles.flex.apps.apg.iam.ewc" = { issuers: { dids: [], role: "Installer" } } ``` Given the roles have the following addresses as their members: ```json= Authority = ["address1"] DSO = ["address2"] Installer = ["address3"] ``` Assuming we can go the happy path. When the user requests the prosumer role, the claim will be issued by the only installer `0xaddress3` but it will contain multiple signed claims in order to create a proof which contains the entire hierarchy up to the root address `0xaddress0`. ###### Exemplary structure might look like this: ```json= proof = { "Prosumer.roles.flex.apps.apg.iam.ewc": { user: "userAddress" issuers: [ "{ethSign of [Prosumer-userAddress] from address3 as did:ethr:address3}", "{ethSign of [Installer-address3] from address2 as did:ethr:address2}", "{ethSign of [DSO-address2] from address1 as did:ethr:address1}", "{ethSign of [Authority-address1] from address0 as did:ethr:address0}" ] } } ``` This proof can be provided to a smart contract which can check the role definition and validate that all the signers have indeed received their authorisation from a correct authority. As each of the addresses have received their proof when they received their role, including the proof hierarchy is a simple matter of fetching it from one's DID document. ## Role management smart-contract In order to make Switchboard roles available in smart contracts, a single `Roles Manager` smart contract is deployed which can answer the question `hasRole(address user, string role) returns (uint)` The contract contains a mapping like `mapping (bytes32 => mapping (address => uint)) addressRoles` which allows to easily find out if an address has a role. The `uint` returns the expiry timestamp. In order to make an entry in the mapping, a registration request must be submitted. The request contains the proof received during the role issuance request. It is important that this registration request can be submitted on behalf of a user. I.e. the proof remains valid indepently of the `message.sender`. ### Resolver for Role namespace In order to find out if a signature is correct, the resolver of the role's ENS name must be extended to include a struct whith the same information it contains today in JSON. The resolver for all the namespaces is currently an [EIP 634](https://eips.ethereum.org/EIPS/eip-634) contract which allows to store text attributes. In order to make the role definition smart contract readable, the functionality must be extended to store the issuer settings in a solidity struct. The struct should contain just two attributes: ```json= struct issuers { address[] dids bytes32 role } ``` This diverges from the JSON role definition in which the `dids` attribute contains actual `DID` elements. The `role` attribute contains the `node` value of the role, reducing the gas requirements for the verification as it is not necessary to hash the string before the node can be accessed. ### Process It should be noted that this process only works if all the DIDs are registered (anchored) on the EWC as it is not possible to fetch the signing key for a DID on a foreign chain. This sequence diagram represents the sequence of events in the registration function of the Roles Management smart contract. ```mermaid sequenceDiagram participant usr as Registrant participant rm as Roles Management participant rd as Role Definition participant id as Identity Registry usr ->> rm: request registration note over rm, id: the loop starts from the root signer which must be a DID and not a role loop for each entry in the issuers list rm ->> rm: get signing key from signature rm ->> id: request list of signing keys for signer did id -->> rm: array of valid keys alt find signing key in valid keys rm ->> rm: store signer did with its role else rm -->> usr: invalid signature error end end ``` ```mermaid graph TD subgraph verify signer getIssuer[get issuer for role] --> isAddress{is issuer a role} isAddress --> |issuer is a role| retVerify[verify signature</br>for next signer] isAddress --> |issuer is a list of addresses| retRes[return signer is</br>in issuer list] retVerify --> getIssuer retRes --> return end subgraph registration request getSigner[get signer from proof] --> verifySigner[verify signer] verifySigner -- call with entire proof --- getIssuer verifySigner --> signerOk signerOk{signer verified} signerOk --> |yes| storeUser[store user in mapping] signerOk --> |no| rejectRequest[reject request] rejectRequest --> stopReg[stop] storeUser --> stopReg end ``` What this process does is making sure that there is an actual address as the root authority for granting access and that all the roles have been granted according to the definitions. The reason why we should not require all the users to be registered ### Using in smart-contract The role assignment can be used in a smart contract by simply calling the `hasRole(address user, string role)` function on the Role Management smart contract. Only one Role Management smart-contract is required for a chain. As it is permissionless, there is no sense in setting up multiple versions of it. This helps to make it easy to use as there is no need to make the address dynamic. ## Role revocation A simple version of the revocation mechanism is to allow anyone with the right to issue a role to also revoke it. The revocation is performed by updating the expiry timestamp in the users to roles mapping `mapping (bytes32 => mapping (address => uint))`. The timestamp should be set to the `block.timestamp` of the revocation transaction in order to avoid a situation in which the role attribution is set to `0` which would be equal to `the user was never granted the role`. The revocation involves that the `msg.sender` has registered has having the issuer role and the `hasRole` function can be called. It does not make sense to not register the issuer as all data provided to a smart contract function call is public and the fact that the user is an admin can be deduced from the fact that they revoked the claim. Hence there is no reason to try and preserve the privacy of the admin's identity by not registering them in the smart-contract.