Introduction to Muon Apps

What is a Muon App?

Apps in the decentralized environment, called dApps, are made up of a smart contract and a client, that is, a frontend user interface. There are, however, dapps that need an extra component to provide data from off-chain sources, i.e. an oracle. DApps for different purposes have different oracles that read and process data from sources and return signed responses.

At a first glance, one might think that developing an oracle is the simplest stage in developing a dApp; all there is to do is implementing a function that fetches and processes data to generate a signed output suitable to be fed to its smart contract.

However, it is not that simple. Data feeds provided by oracles are vulnerable to attacks and manipulation. The server on which an oracle component is run may be hacked and the private key used to sign data feeds can be abused to feed manipulated data to the smart contract. This is why, in addition to developing a simple oracle function, dApps need to deal with implementing solutions for complicated security issues.

As a viable alternative, dApps can focus on the development of the oracle function as a Muon app and depend on the multi-layer security scheme that Muon has devised. In simple words, a Muon app refers to an oracle app that is deployed and runs on the Muon network to fetch and process data and generate an output that can be fed to a smart contract reliably.

A Simple Oracle App

Muon apps are currently developed in JS; other programming languages will gradually be added. Here is a sample oracle app whose job is to fetch the price of ETH from Coinbase API.

const { axios } = MuonAppUtils

module.exports = {
  APP_NAME: 'simple_oracle',

  onRequest: async function(request){
    let { method } = request
    switch (method) {
      case 'eth-price':
        const response = await axios.get('https://api.coinbase.com/v2/exchange-rates?currency=ETH')
        return {
          price: parseInt(response.data['rates']['USD']),
        }

      default:
        throw `Unknown method ${method}`
    }
  },

  signParams: function(request, result){
    let { method } = request;
    let { price } = result;
    switch (method) {
      case 'eth-price':
        return [
          { type: 'uint256', value: price }
        ]
      default:
        throw `Unknown method ${method}`
    }
  }
}

A Muon app is a module that exports two functions: onRequest and signParams. The first fetches data, does any necessary processing and returns any data needed to be fed to the smart contract. The second function lists all the parameters that are to be included in the signed message and their types.

Deploying the App

Having prepared the simple oracle app, the developer needs to run a local network to test it. There are two prerequisites that should be installed: Mongo and Redis. After these two have been installed, the following steps should be followed to run the network.

The first step is to clone Muon node’s repository and checkout the testnet branch through:

git clone git@github.com:muon-protocol/muon-node-js.git --recurse-submodules
cd muon-node-js
git checkout testnet

The next step is to install required node modules as follows:

npm install

The network should be initialized using a number of nodes. For instance, a network of 4 nodes is created with the following command, where a request can be signed with 3 of them.

npm run devnet-init -- -t=3 -n=4 -infura=<your-infura-project-id>

‍‍‍As many Muon apps need to connect to Ethereum Mainnet and its sidechains, the developer’s Infura project ID should be added as well. The developer should then place the app in the apps/general/ folder and run the network with this command:

npm run devnet-run -- -n=3

The first time the app is run, it should be deployed on the network. To do so, use the following commands:

./node_modules/.bin/ts-node ./src/cmd config set url "http://localhost:8000/v1"
./node_modules/.bin/ts-node ./src/cmd app deploy "simple_oracle"

Now that the app has been deployed, the developer can query the app and get signed responses from it. To query the app, curl, for instance, can be use

curl "http://localhost:8000/v1/?app=simple_oracle&method=eth-price"

Note

When the testing is done and the app is about to be deployed on the public networks, the following steps should be observed:

  • Muon apps repository is forked.

  • The app is added to the forked repository.

  • A pull request is submitted to the repository

With this process, the app will be reviewed and added to Muon apps.

Verifying Signatures on the Contract

The TSS which Muon network generates is of Schnorr type and there are no built-in functions on Ethereum for its verification. There are, however, libraries that help verify the signature with a small amount of gas fee. Muon has provided such a library for dApps using Muon. These should import it into their smart contracts, inherit the MuonClient contract available here, and use the muonVerify function to verify the signature. Here is a sample:

// SPDX-License-Identifier: MIT
   pragma solidity ^0.8.0;

   import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

   import "./MuonClient.sol";

   contract SampleApp is MuonClient {
       using ECDSA for bytes32;

       constructor(
           uint256 _muonAppId,
           PublicKey memory _muonPublicKey
       ) MuonClient(_muonAppId, _muonPublicKey){

       }

       function verifyTSS(
           uint256 price,
           bytes calldata reqId,
           SchnorrSign calldata sign
       ) public{
           bytes32 hash = keccak256(
               abi.encodePacked(
                   muonAppId,
                   reqId,
                   price
               )
           );
           bool verified = muonVerify(reqId, uint256(hash), sign, muonPublicKey);
           require(verified, "TSS not verified");
       }
   }

In addition to the TSS layer, Muon network has another security layer called Shield Nodes. A shield node makes use of Elliptic Curve Digital Signature Algorithm (ECDSA) signature which can be verified by built-in functions on Ethereum.

...
contract SampleApp is MuonClient {

    address shieldNode = msg.sender; // by default
    ...
    function verifyTSSAndShieldNode(
        uint256 price,
        bytes calldata reqId,
        SchnorrSign calldata sign,
        bytes calldata shieldNodeSign
    ) public {
        bytes32 hash = keccak256(
            abi.encodePacked(
                muonAppId,
                reqId,
                price
            )
        );
        bool verified = muonVerify(reqId, uint256(hash), sign, muonPublicKey);
        require(verified, "TSS not verified");

        hash = hash.toEthSignedMessageHash();
        address signer = hash.recover(shieldNodeSign);
        require(signer == shieldNode, "Shield node is not valid");

    }
}

This sample illustrates how shield node signature can be verified in addition to threshold signature in one function call.