Managing authorizations & delegation on EOS

in #eosio6 years ago

This post was originally posted on Azarus's medium: https://medium.com/@azarusio

Azarus is a tokenized game platform built on EOS. As we foray into this EOS technology stack, we wanted to share the build process and discoveries.
Follow us on:
web: https://azarus.io
twitter: https://twitter.com/azarusio
discord: https://discord.gg/ESma8zx

As with any blockchain, every transaction sent to the nodes is signed by the private key of the caller. If you coded in Ethereum/Solidity, you probably had to add methods to your contracts setting & updating addresses that would be the only ones allowed some critical functions. One such example is the ceo kill-switch in the cryptokitties contracts…. which may only be called by the CEO.

The Azarus game challenges — internally referred to as “rings” — had some special requirements:

  • The ring contract must be able to receive the deposit of AZA coins and allow a payout to the winner or a refund if the contract isn’t executed
  • The deposited amount should not be accessible from other rings
  • The client Dapp may not operate any coin transfer aside from depositing the initial funds
  • The oracle in charge of reporting the results of the game should be able to initiate the payout.
    This seems like a prefect playground to verify our understanding of the EOS authorization & delegation scheme.

The approach we took narrowed down to creating “buckets” to deposit the funds… and to treat those buckets as accounts instead of contracts which would have been the expected approach on other blockchains.

As we focus on the permission-side of things, the code below is based on eosjs and we’ll ignore the actual content of the contracts. We also prototyped this using the EOS coin as the value store, in the actual production version we’ll obviously be using our AZA token instead.

Setting up the contracts

We start by publishing our ring contracts

const eosioTokenClientAccount = await eos.newaccount({
  creator: 'eosio',
  name: eosioToken,
  owner: eosioTokenClientPublic,
  active: eosioTokenClientPublic,
  recovery: 'eosio',
  deposit: '0 EOS'});
const eosioTokenClientContractPath = eosContractsPath + '/eosio.token/eosio.token';
const wast = fs.readFileSync(eosioTokenClientContractPath + '.wast')
const abi = fs.readFileSync(eosioTokenClientContractPath + '.abi')
const actions = await eos.getActions({
  account_name:'eosio',
  pos: -1,
  offset: 0
});
try {
  const res_code = await  eos.setcode(eosioToken, 0, 0, wast);
  const res_abi = await eos.setabi(eosioToken, JSON.parse(abi));
} catch(e) {
  console.log(e);
}
let eosioTokenContract = await eos.contract(eosioToken);

Then we create a set of 10 buckets as new accounts.

const nbMaxBucket = 10;
var nbBucket = 0;
for (i=1; i<6; i++) {
  for (j=1; j<6; j++) {
  const azaOracleBucket = 'azabucket' + i.toString() + j.toString();
  const azaOracleBucketAccount = await eos.newaccount({
    creator: azaOracle,
    name: azaOracleBucket,
    owner: azaBucketPublic,
    active: azaBucketPublic,
    recovery: azaOracle,
    deposit: '0 EOS'});
  console.log("Account " + azaOracleBucket + " created");
  const bucket_account = await eos.getAccount(azaOracleBucket);
  const permRes = await permission(azaOracle, azaOracleBucket, azaBucketPublic);
  const bucket_account2 = await eos.getAccount(azaOracleBucket);
  nbBucket++;
  if (nbBucket >= nbMaxBucket) {
      break;
    }
  }
  if (nbBucket >= nbMaxBucket) {
      break;
    }
  }
  console.log("Account and Contract created for " + azaOracle);
} catch(err) {
  console.log(err);
}

Authorizing the Oracle

This next piece grants authority to the Oracle to pay out the winner of a ring, ultimately accessing the funds in the bucket. This call must be done from the same “owner” account that created the bucket.

async function permission(delegate, bucketName, publicKey) {
  try {
    const permRes = await eos.updateauth({
      account: bucketName,
      permission: "active",
      parent: "owner",
      data: {
        threshold: 1,
        keys: [{key: publicKey, weight: 1}],
        accounts: [{permission:{ actor:delegate, permission:"active"},    weight:1}]
      },
      delay: 0
    });
    return permRes;
  } catch(e) {
    console.log(e);
    return e;
  }
}

What’s really outstanding here, is that without ever having to give the Oracle access to the private key of the bucket, we are allowing him an access as an “active” delegate.

As an added feature you can also cascade those authorizations, creating for instance an “azarus” permission allowing delegate2 to access the bucket too as a delegate to the initial delegate.

const permRes2 = await eos.updateauth({
  account: bucketName,
  permission: "azarus",
  parent: "active",
  data: {
    threshold: 1,
    keys: [{key: publicKey, weight: 1}],
    accounts: [{permission:{ actor:delegate2, permission:"active"}, weight:1}]
  },
  delay: 0
});

Spinning the wheels

We now have buckets, where you can deposit funds, and where an Oracle should be able to trigger a payout… let’s make it happen

Here’s how you can send coins to a bucket — nothing special here it’s a simple transfer of EOS coins.

const eosioTokenContract = await eos.contract('eosio.token');
const azaOracleBucket = 'azabucket11'
let token_transfer_user = await eosioTokenContract.transfer({
  from: userName,
  to: azaOracleBucket,
  quantity: prize + ' EOS',
  memo: 'deposit'},
  { authorization: userName });
console.log("Payment from " + userName + " TO " + azaOracle + " for " + prize + ' -> OK');

And now the final touch: the Oracle calling the bucket to proceed to payout!

const eosioTokenContract = await eos.contract('eosio.token');
const azaOracleBucket = 'azabucket11'
const bucket_account2 = await eos.getAccount(azaOracleBucket);
let token_transfer_user = await eosioTokenContract.transfer({
  from: azaOracleBucket,
  to: beneficiary,
  quantity: 100 + ' EOS',
  memo: 'deposit'},
  { 
    authorization: [{
      actor: azaOracleBucket, permission: 'active',
   }],
  }
);

Here the Oracle was able to proceed to payout only calling the bucket using its “azaOracleBucket” identity which was setup as a delegate previously.

Here’s a schematics of the architecture we just put together focusing on the location of the various private keys (pKeys):


Private keys location

Conclusion

It’s pretty impressive to see how with just a few calls, we can create accounts and authorize elements of the system to interact with it without the need to ever reveal or share their private key. Ultimately this removes a lot of complexity from the contracts themselves who are not in charge anymore of the access control and can focus on processing the transactional data letting the EOS infrastructure manage the access control!

Share your thoughts and feel free to challenge our approach — this is a new platform and we’re learning as we’re building Azarus… and as EOS gets built!

Sort:  

Congratulations @alexksso! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

You published your First Post
You got a First Vote

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @alexksso! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

Click here to view your Board

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @alexksso! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!