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