github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-030-authz-module.md (about) 1 # ADR 030: Authorization Module 2 3 ## Changelog 4 5 * 2019-11-06: Initial Draft 6 * 2020-10-12: Updated Draft 7 * 2020-11-13: Accepted 8 * 2020-05-06: proto API updates, use `sdk.Msg` instead of `sdk.ServiceMsg` (the latter concept was removed from Cosmos SDK) 9 * 2022-04-20: Updated the `SendAuthorization` proto docs to clarify the `SpendLimit` is a required field. (Generic authorization can be used with bank msg type url to create limit less bank authorization) 10 11 ## Status 12 13 Accepted 14 15 ## Abstract 16 17 This ADR defines the `x/authz` module which allows accounts to grant authorizations to perform actions 18 on behalf of that account to other accounts. 19 20 ## Context 21 22 The concrete use cases which motivated this module include: 23 24 * the desire to delegate the ability to vote on proposals to other accounts besides the account which one has 25 delegated stake 26 * "sub-keys" functionality, as originally proposed in [\#4480](https://github.com/cosmos/cosmos-sdk/issues/4480) which 27 is a term used to describe the functionality provided by this module together with 28 the `fee_grant` module from [ADR 029](./adr-029-fee-grant-module.md) and the [group module](https://github.com/cosmos/cosmos-sdk/tree/main/x/group). 29 30 The "sub-keys" functionality roughly refers to the ability for one account to grant some subset of its capabilities to 31 other accounts with possibly less robust, but easier to use security measures. For instance, a master account representing 32 an organization could grant the ability to spend small amounts of the organization's funds to individual employee accounts. 33 Or an individual (or group) with a multisig wallet could grant the ability to vote on proposals to any one of the member 34 keys. 35 36 The current implementation is based on work done by the [Gaian's team at Hackatom Berlin 2019](https://github.com/cosmos-gaians/cosmos-sdk/tree/hackatom/x/delegation). 37 38 ## Decision 39 40 We will create a module named `authz` which provides functionality for 41 granting arbitrary privileges from one account (the _granter_) to another account (the _grantee_). Authorizations 42 must be granted for a particular `Msg` service methods one by one using an implementation 43 of `Authorization` interface. 44 45 ### Types 46 47 Authorizations determine exactly what privileges are granted. They are extensible 48 and can be defined for any `Msg` service method even outside of the module where 49 the `Msg` method is defined. `Authorization`s reference `Msg`s using their TypeURL. 50 51 #### Authorization 52 53 ```go 54 type Authorization interface { 55 proto.Message 56 57 // MsgTypeURL returns the fully-qualified Msg TypeURL (as described in ADR 020), 58 // which will process and accept or reject a request. 59 MsgTypeURL() string 60 61 // Accept determines whether this grant permits the provided sdk.Msg to be performed, and if 62 // so provides an upgraded authorization instance. 63 Accept(ctx sdk.Context, msg sdk.Msg) (AcceptResponse, error) 64 65 // ValidateBasic does a simple validation check that 66 // doesn't require access to any other information. 67 ValidateBasic() error 68 } 69 70 // AcceptResponse instruments the controller of an authz message if the request is accepted 71 // and if it should be updated or deleted. 72 type AcceptResponse struct { 73 // If Accept=true, the controller can accept and authorization and handle the update. 74 Accept bool 75 // If Delete=true, the controller must delete the authorization object and release 76 // storage resources. 77 Delete bool 78 // Controller, who is calling Authorization.Accept must check if `Updated != nil`. If yes, 79 // it must use the updated version and handle the update on the storage level. 80 Updated Authorization 81 } 82 ``` 83 84 For example a `SendAuthorization` like this is defined for `MsgSend` that takes 85 a `SpendLimit` and updates it down to zero: 86 87 ```go 88 type SendAuthorization struct { 89 // SpendLimit specifies the maximum amount of tokens that can be spent 90 // by this authorization and will be updated as tokens are spent. This field is required. (Generic authorization 91 // can be used with bank msg type url to create limit less bank authorization). 92 SpendLimit sdk.Coins 93 } 94 95 func (a SendAuthorization) MsgTypeURL() string { 96 return sdk.MsgTypeURL(&MsgSend{}) 97 } 98 99 func (a SendAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptResponse, error) { 100 mSend, ok := msg.(*MsgSend) 101 if !ok { 102 return authz.AcceptResponse{}, sdkerrors.ErrInvalidType.Wrap("type mismatch") 103 } 104 limitLeft, isNegative := a.SpendLimit.SafeSub(mSend.Amount) 105 if isNegative { 106 return authz.AcceptResponse{}, sdkerrors.ErrInsufficientFunds.Wrapf("requested amount is more than spend limit") 107 } 108 if limitLeft.IsZero() { 109 return authz.AcceptResponse{Accept: true, Delete: true}, nil 110 } 111 112 return authz.AcceptResponse{Accept: true, Delete: false, Updated: &SendAuthorization{SpendLimit: limitLeft}}, nil 113 } 114 ``` 115 116 A different type of capability for `MsgSend` could be implemented 117 using the `Authorization` interface with no need to change the underlying 118 `bank` module. 119 120 ##### Small notes on `AcceptResponse` 121 122 * The `AcceptResponse.Accept` field will be set to `true` if the authorization is accepted. 123 However, if it is rejected, the function `Accept` will raise an error (without setting `AcceptResponse.Accept` to `false`). 124 125 * The `AcceptResponse.Updated` field will be set to a non-nil value only if there is a real change to the authorization. 126 If authorization remains the same (as is, for instance, always the case for a [`GenericAuthorization`](#genericauthorization)), 127 the field will be `nil`. 128 129 ### `Msg` Service 130 131 ```protobuf 132 service Msg { 133 // Grant grants the provided authorization to the grantee on the granter's 134 // account with the provided expiration time. 135 rpc Grant(MsgGrant) returns (MsgGrantResponse); 136 137 // Exec attempts to execute the provided messages using 138 // authorizations granted to the grantee. Each message should have only 139 // one signer corresponding to the granter of the authorization. 140 rpc Exec(MsgExec) returns (MsgExecResponse); 141 142 // Revoke revokes any authorization corresponding to the provided method name on the 143 // granter's account that has been granted to the grantee. 144 rpc Revoke(MsgRevoke) returns (MsgRevokeResponse); 145 } 146 147 // Grant gives permissions to execute 148 // the provided method with expiration time. 149 message Grant { 150 google.protobuf.Any authorization = 1 [(cosmos_proto.accepts_interface) = "cosmos.authz.v1beta1.Authorization"]; 151 google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; 152 } 153 154 message MsgGrant { 155 string granter = 1; 156 string grantee = 2; 157 158 Grant grant = 3 [(gogoproto.nullable) = false]; 159 } 160 161 message MsgExecResponse { 162 cosmos.base.abci.v1beta1.Result result = 1; 163 } 164 165 message MsgExec { 166 string grantee = 1; 167 // Authorization Msg requests to execute. Each msg must implement Authorization interface 168 repeated google.protobuf.Any msgs = 2 [(cosmos_proto.accepts_interface) = "cosmos.base.v1beta1.Msg"];; 169 } 170 ``` 171 172 ### Router Middleware 173 174 The `authz` `Keeper` will expose a `DispatchActions` method which allows other modules to send `Msg`s 175 to the router based on `Authorization` grants: 176 177 ```go 178 type Keeper interface { 179 // DispatchActions routes the provided msgs to their respective handlers if the grantee was granted an authorization 180 // to send those messages by the first (and only) signer of each msg. 181 DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs []sdk.Msg) sdk.Result` 182 } 183 ``` 184 185 ### CLI 186 187 #### `tx exec` Method 188 189 When a CLI user wants to run a transaction on behalf of another account using `MsgExec`, they 190 can use the `exec` method. For instance `gaiacli tx gov vote 1 yes --from <grantee> --generate-only | gaiacli tx authz exec --send-as <granter> --from <grantee>` 191 would send a transaction like this: 192 193 ```go 194 MsgExec { 195 Grantee: mykey, 196 Msgs: []sdk.Msg{ 197 MsgVote { 198 ProposalID: 1, 199 Voter: cosmos3thsdgh983egh823 200 Option: Yes 201 } 202 } 203 } 204 ``` 205 206 #### `tx grant <grantee> <authorization> --from <granter>` 207 208 This CLI command will send a `MsgGrant` transaction. `authorization` should be encoded as 209 JSON on the CLI. 210 211 #### `tx revoke <grantee> <method-name> --from <granter>` 212 213 This CLI command will send a `MsgRevoke` transaction. 214 215 ### Built-in Authorizations 216 217 #### `SendAuthorization` 218 219 ```protobuf 220 // SendAuthorization allows the grantee to spend up to spend_limit coins from 221 // the granter's account. 222 message SendAuthorization { 223 repeated cosmos.base.v1beta1.Coin spend_limit = 1; 224 } 225 ``` 226 227 #### `GenericAuthorization` 228 229 ```protobuf 230 // GenericAuthorization gives the grantee unrestricted permissions to execute 231 // the provided method on behalf of the granter's account. 232 message GenericAuthorization { 233 option (cosmos_proto.implements_interface) = "Authorization"; 234 235 // Msg, identified by it's type URL, to grant unrestricted permissions to execute 236 string msg = 1; 237 } 238 ``` 239 240 ## Consequences 241 242 ### Positive 243 244 * Users will be able to authorize arbitrary actions on behalf of their accounts to other 245 users, improving key management for many use cases 246 * The solution is more generic than previously considered approaches and the 247 `Authorization` interface approach can be extended to cover other use cases by 248 SDK users 249 250 ### Negative 251 252 ### Neutral 253 254 ## References 255 256 * Initial Hackatom implementation: https://github.com/cosmos-gaians/cosmos-sdk/tree/hackatom/x/delegation 257 * Post-Hackatom spec: https://gist.github.com/aaronc/b60628017352df5983791cad30babe56#delegation-module 258 * B-Harvest subkeys spec: https://github.com/cosmos/cosmos-sdk/issues/4480