Contracts can define their own accounts and authenticate transactions for those accounts. This makes lightweight accounts more useful, enables more-flexible account structures, and helps accounts from other chains interact with eosio-based chains.
EOSIO blockchains have a flexible and powerful account structure, but we see some areas to improve:
- Developers desire a way to onboard users at a much smaller individual cost (RAM, net, and cpu per user).
- IBC mechanisms need a way to support accounts from other chains.
- Users need a convenient way to limit risk. e.g. allowing frequent small transfers while limiting large transfers.
What if contracts could define their own account structures? What if these contracts could authenticate transactions from those accounts? What if they could do their own resource billing? What if these contracts didn’t have to be privileged? This proposal, which builds on the Query Consumption, Subjective Data, and Contract Pays proposals, grants contracts this capability.
There are 3 changes in this consensus upgrade:
- A new intrinsic
require_key
asserts that a given public key was recovered from transaction signatures. - Nodeos will no longer reject transactions with signatures not used by the native authorizations, if those keys are checked by
require_key
. - Nodeos will allow transactions to omit native authorizations, if a contract accepts the charges using
accept_charges
. This builds on the Contract Pays proposal.
- A user without a native account creates a transaction with this action:
account: "gamecontract"
name: "playgame1"
data: Arguments needed by game, including a user name.
authorization: empty
- The user signs the transaction with a key that gamecontract knows about
- The user pushes the transaction
- gamecontract calls
accept_charges
before leeway expires - gamecontract uses
require_key
to authenticate the user - gamecontract verifies that no other actions are present in the transaction. This prevents it from paying for other contracts.
It can do this by verifying
get_sender
returns 0, the number of non-context-free actions returned byget_num_actions
is 1, and the number of context-free actions returned byget_num_actions
is 0. - gamecontract executes the game logic
- A user with an account managed by "myauthmgr" creates a transaction with this action:
account: "myauthmgr"
name: "execute"
data: actions with authorizations
authorization: empty
- The user signs the transaction with a key that myauthmgr knows about
- The user pushes the transaction
- myauthmgr calls
accept_charges
before leeway expires - myauthmgr checks the claimed authorizations using
require_key
- myauthmgr verifies that no other native actions are present in the transaction. This allows it to accurately track resource consumption.
It can do this by verifying
get_sender
returns 0, the number of non-context-free actions returned byget_num_actions
is 1, and the number of context-free actions returned byget_num_actions
is 0. - myauthmgr updates
accept_charges
using the user’s available resources - myauthmgr executes inline actions specified by the user. It includes an authorization attestation in each inline action. The contracts receiving these actions rely on the attestation. See Forwarding Authentication for details on this step.
- Inside an inline action, myauthmgr calls
sync_context_free
, checksget_trx_net_bill
andget_trx_cpu_bill
, then updates the user’s resource consumption. This inline action executes last.
void require_key(char* pub, size_t publen);
This intrinsic verifies that a given public key was recovered from transaction signatures. It aborts the transaction if either the key wasn't recovered, or if it's called from a deferred transaction.
Nodeos normally rejects transactions which have recovered keys not needed by the native authorizations. Nodeos
will no longer reject these transactions, if all the extra keys were referenced by require_key
. It verifies
all keys are used to prevent a resource attack. If someone intercepted a transaction, added unneeded signatures,
then delivered that transaction to the scheduled producer before the original, that modified transaction would
spend more CPU than necessary. Since all recovered keys must be referenced, this attack won't work.
Contract Pays adds this intrinsic:
bool accept_charges(
uint32_t max_net_usage_words, // Maximum NET usage to charge
uint32_t max_cpu_usage_ms // Maximum CPU usage to charge
);
Nodeos normally rejects transactions which have no authorizations. After this consensus upgrade, nodeos will no longer reject these transactions if a contract accepts the charges during the leeway.
No contract may accept charges during a deferred transaction.