read

How to use multisignatures to protect funds and secure user based transactions using NCC API.


Thanks to QM who orignally wrote this tutorial here.

Part I - Automated Co-Signatures

Multisig is one of the top features offered by NEM. Easy to use and well designed, it allows you to secure an account by requiring multiples signatures to validate a transaction. When an account is converted to multisig, nobody, even his creator, can initiate a transaction directly from it. Only accounts designed as cosignatories can do it.

You can even use M of N to require the number of signatures you want among the total cosignatories. For example, you have 4 cosignatories for an account, you can choose to validate a transaction with only 3/4 or 2/4 signatures. Also, on chain notifications give almost instant appearance of a transaction requiring signature on the blockchain, it will be really useful for what we're going to do as all automated cosignatories can now communicate only through the blockchain.

NEM has made coding easy around NCC and NIS with their simple but powerful APIs:
NIS API
NCC API

We'll use NCC API here because it can do the signature on its own and so, no need to send a private key to the remote NIS, we'll only use it to propagate the signed transaction.

Now we're fixed about the system design NEM side, we have to choose a programming language to code the part that'll automate the transactions, watch the amounts, daily amount and so on. It needs to be Json compliant. For this experience I choose NodeJS because I am very new to it and it's a pretty good exercise, but you can choose one you are most comfortable with.

Before we start, let's design our program.

We want it to:
- Use 2 of 2 multisig account - Be secure, we'll use password encrypted configuration - Sign transactions every 10 minutes - Sign only < 1000 XEM transactions - Sign only a total daily amount of 100'000 XEM

That's a good start. I wrote here how we set the rules but if you keep it secret in your password encrypted configuration file, your business logic is hidden.

We also need a local NCC running in background connected to a persistent remote NIS. We want to avoid unexpected errors if disconnection.

You can create the 2 of 2 multisig using this tutorial

The cosignatory account we'll use in our automated program must be in a wallet that belong to the local NCC.

We need a secure configuration file. For NodeJS secure-conf is perfect. But, we'll add it later for simplicity.

To automate the program we simply use setInterval function that we set to 10 minutes.

var the_interval = 10 * 60 * 1000; //10 minutes    

    setInterval(function() {
          // Do something every 10 minutes
    }, the_interval);

Now it's done we want to pull unconfirmed transactions from NCC, for that we'll use the API and nodejs2nem

    // include the required class using nodejs2nem
    NEM = require('./NEM.js');

    // create an instance using default configuration options
    var nem = new NEM();

    var the_interval = 10 * 60 * 1000; //10 minutes

    setInterval(function() {

    //We set the cosigatory account we want to watch tx for.
    var data = {
    account: _cosignatoryAccount
    };

    //output a pretty formated JSON text
    var toPrettyJson = function(data) {
    var e = JSON.stringify(data,null,4);
    console.log(e);
    };

    //Get unconfirmed transactions from local NCC
    nem.nccPost('/account/transactions/unconfirmed',data
    ,function(err) {
    console.log(err);
    }
    ,toPrettyJson
    );

    }, the_interval);

Now let's get further and change the callback function to examine the result.

    // include the required class using nodejs2nem
    NEM = require('./NEM.js');

    // create an instance using default configuration options
    var nem = new NEM();

    var the_interval = 10 * 60 * 1000; //10 minutes

    setInterval(function() {

    //We set the cosigatory account we want to watch tx for.
    var data = {
    account: _cosignatoryAccount
    };

    //Our new function
    var XEMsign = function(data) {

    //output a pretty formated JSON text
    var d = JSON.stringify(data,null,4);
    //console.log(d); //show all the pull

    //parsing Json
    obj = JSON.parse(d);

    //Get total unconfirmed tx number
    totalUnconfirmed = obj.transactions.length;

    if (totalUnconfirmed == 0)
    {
    console.log("\n");
    console.log("No transaction to sign. Waiting...");
    }
    else
    {
    console.log("Something to sign !");
    }

    }; //End XEmsign function

    //Get unconfirmed transactions from local NCC
    nem.nccPost('/account/transactions/unconfirmed',data
    ,function(err) {
    console.log(err);
    }
    ,XEMsign //New callback
    );

    }, the_interval);

Simple isn't it? We already watch an account for unconfirmed transactions in very few lines of basic code.

You can try it right now if you want. Try with no transaction to sign, then with one you have initiated from the second cosignatory

Next step, extracting inner hashes from transactions in order to sign them.

    // include the required class using nodejs2nem
    NEM = require('./NEM.js');

    // create an instance using default configuration options
    var nem = new NEM();

    var the_interval = 10 * 60 * 1000; //10 minutes

    setInterval(function() {

    //We set the cosigatory account we want to watch tx for.
    var data = {
    account: _cosignatoryAccount
    };

    //Our new function
    var XEMsign = function(data) {

    //output a pretty formated JSON text
    var d = JSON.stringify(data,null,4);
    //console.log(d); //show all the pull

    //parsing Json
    obj = JSON.parse(d);

    //Get total unconfirmed tx number
    totalUnconfirmed = obj.transactions.length;

    if (totalUnconfirmed == 0)
    {
    console.log("\n");
    console.log("No transaction to sign. Waiting...");
    }
    else
    {
    var i;
    var dataHash = [];
    for (i = 0; i < totalUnconfirmed; i++) //We batch tx
    {
    //Get inner hashes
    dataHash[i] = obj.transactions[i].innerHash.data;
    console.log("Transaction hash:");
    console.log(dataHash[i]);


    // MultisigSignatureRequest
    var transac = [];
    transac[i] = {
    wallet: _wallet,
    password: _password,
    account: _cosignatoryAccount,
    multisigAddress: _multisigAddress,
    innerHash: {
    data: dataHash[i]
    },
    hoursDue: _hoursDue,
    fee: _fee
    };

    //output a pretty formated JSON text
    var toPrettyJson = function(transac) {
    var e = JSON.stringify(transac,null,4);
    console.log(e);
    };

    //Sign transaction
    nem.nccPost('/wallet/account/signature/send',transac[i]
    ,function(err) {
    console.log(err);
    }
    ,toPrettyJson
    );

    //Batch report
    console.log("Total transactions: ");
    console.log(totalUnconfirmed);
    console.log("Done, waiting...");

    } //endFor
    } //endElse
    }; //end XEMsign function

    //Get unconfirmed transactions from local NCC
    nem.nccPost('/account/transactions/unconfirmed',data
    ,function(err) {
    console.log(err);
    }
    ,XEMsign
    );
    }, the_interval);

We are now signing unconfirmed transactions every 10 minutes if any. There is no rules yet so let's add them. We'll need to extract transaction amount for our max amount rule and daily amount. Also, we will add a timer to reset daily amount after 24 hours.

    // include the required class using nodejs2nem
    NEM = require('./NEM.js');

    // create an instance using default configuration options
    var nem = new NEM();

    var the_interval = 10 * 60 * 1000; //10 minutes

    //Starting with 0
    var dailyAmount = 0;
    var amount = [];
    var time = 0;

    setInterval(function() {

    //If 24h => reset dailyAmount
    if (time > _dailyTimer * 60 * 1000)
    {
    dailyAmount = 0;
    time = 0;
    }

    //We set the cosigatory account we want to watch tx for.
    var data = {
    account: _cosignatoryAccount
    };

    //Our new function
    var XEMsign = function(data) {

    //output a pretty formated JSON text
    var d = JSON.stringify(data,null,4);
    //console.log(d); //show all the pull

    //parsing Json
    obj = JSON.parse(d);

    //Get total unconfirmed tx number
    totalUnconfirmed = obj.transactions.length;

    if (totalUnconfirmed == 0)
    {
    console.log("\n");
    console.log("No transaction to sign. Waiting...");
    time += _timer * 60 * 1000;
    }
    else
    {
    var i;
    var dataHash = [];
    for (i = 0; i < totalUnconfirmed; i++) //We batch tx
    {
    //Get inner hashes
    dataHash[i] = obj.transactions[i].innerHash.data;
    console.log("Transaction hash:");
    console.log(dataHash[i]);

    //parsing Json to get the amount for daily amount
    dailyAmount += obj.transactions[i].inner.amount/1000000;
    console.log("Dayli amount:");
    console.log(dailyAmount);

    //parsing Json to get the amount
    amount[i] = obj.transactions[i].inner.amount/1000000;
    console.log("Transaction amount:");
    console.log(amount[i]);


    // MultisigSignatureRequest
    var transac = [];
    transac[i] = {
    wallet: _wallet,
    password: _password,
    account: _cosignatoryAccount,
    multisigAddress: _multisigAddress,
    innerHash: {
    data: dataHash[i]
    },
    hoursDue: _hoursDue,
    fee: _fee
    };

    //output a pretty formated JSON text
    var toPrettyJson = function(transac) {
    var e = JSON.stringify(transac,null,4);
    console.log(e);
    };

    //Maximal Amount is _maxAmount XEM per tx
    if (amount[i] > _maxAmount)
    {
    console.log("There is a problem, only n XEMs transaction max allowed !");
    console.log("Following Transaction cause problems:");
    console.log(dataHash[i]);
    throw new Error('Something goes wrong !'); //In this case we stop cosigning
    }

    //Maximum dayliAmount is _maxDayliAmount XEM
    else if (dailyAmount < _maxDayliAmount)
    {
    //Sign transaction
    nem.nccPost('/wallet/account/signature/send',transac[i]
    ,function(err) {
    console.log(err);
    }
    ,toPrettyJson
    );

    //Batch report
    console.log("Total transactions: ");
    console.log(totalUnconfirmed);
    console.log("Total amount: ");
    console.log(dayliAmount);
    time += _timer * 60 * 1000;
    console.log("Done, waiting...");
    }
    else{
    console.log("MAXIMAL AMOUNT REACHED !");
    time += _timer * 60 * 1000;
    }
    } //endFor
    } //endElse
    }; //end XEMsign function

    //Get unconfirmed transactions from local NCC
    nem.nccPost('/account/transactions/unconfirmed',data
    ,function(err) {
    console.log(err);
    }
    ,XEMsign
    );
    }, the_interval);

Finally let's add our password encrypted configuration file with our wallet connection infos and rules. You probably noticed vars with underscore. It means they're private and so are encrypted in the conf.

If you use nodeJs, to install secure-conf:

npm install secure-conf
You should also install express

npm install express
The configuration file, access.json:

    {
    "wallet" : "yourWallet",
    "walletPassword" : "yourWalletPassword",
    "cosignatoryAccount" : "cosignatoryAccount",
    "multisigAddress" : "multisigAccountAddress",
    "hoursDue" : 24,
    "fee" : 6000000,
    "timer" : 10,
    "dayliTimer" : 1440,
    "maxAmount" : 1000,
    "maxDayliAmount" : 100000
    }

The final program:

    console.log("\n");
    console.log("=================================================");
    console.log("======== XEMsign - Automated cosignature ========");
    console.log("=================================================");
    console.log("\n");

    var fs = require('fs');

    //Check access.json
    if (fs.existsSync('./access.json')) {
    console.log("Enter a password to encrypt access.json:");
    var SecureConf = require('secure-conf');
    var sconf = new SecureConf();

    //Encryption
    sconf.encryptFile(
    "./access.json",
    "./access.json.enc",
    function(err, f, ef, ec) {
    if (err) {
    console.log("failed to encrypt %s, error is %s", f, err);
    } else {
    console.log("\n");
    console.log("encrypt %s to %s complete.", f, ef);
    console.log("encrypted contents are %s", ec);
    console.log("\n");
    console.log("NOW DELETE access.json, EMPTY YOUR BIN AND RESTART XEMsign");
    console.log("\n");
    }
    }
    );
    }
    else{
    console.log("Enter your password to start XEMsign:");
    var SecureConf = require('secure-conf');
    var sconf = new SecureConf();
    var ef = "./access.json.enc";
    var express = require('express');
    var app = express();

    //Decryption
    sconf.decryptFile(ef, function(err, file, content) {
    if (err) {
    console.log("Wrong password !"); //Big error log. Program stop.
    // console.log('Failed to decrypt %s, error is %s', file, err);
    } else {
    console.log("\n");
    console.log("decrypt %s complete.", file);
    }

    //Parsing decrypted content
    var auth = JSON.parse(content);
    var _wallet = auth.wallet;
    var _password = auth.walletPassword;
    var _cosignatoryAccount = auth.cosignatoryAccount;
    var _multisigAddress = auth.multisigAddress;
    var _hoursDue = auth.hoursDue;
    var _fee = auth.fee;
    var _timer = auth.timer;
    var _dailyTimer = auth.timer;
    var _maxAmount = auth.maxAmount;
    var _maxDayliAmount = auth.maxDayliAmount;

    //Setting the timer
    var the_interval = _timer * 60 * 1000;

    console.log("Success...");
    console.log("\n");
    console.log("Starting first check in", _timer, "minutes");

    // include the required class
    NEM = require('./NEM.js');

    // create an instance using default configuration options
    var nem = new NEM();

    //Starting with 0
    var dailyAmount = 0;
    var amount = [];
    var time = 0;

    setInterval(function() {

    //If 24h => reset dailyAmount
    if (time > _dailyTimer * 60 * 1000)
    {
    dailyAmount = 0;
    time = 0;
    }

    //We set the cosigatory account we want to watch tx for.
    var data = {
    account: _cosignatoryAccount
    };

    //Our new function
    var XEMsign = function(data) {

    //output a pretty formated JSON text
    var d = JSON.stringify(data,null,4);
    //console.log(d); //show all the pull

    //parsing Json
    obj = JSON.parse(d);

    //Get total unconfirmed tx number
    totalUnconfirmed = obj.transactions.length;

    if (totalUnconfirmed == 0)
    {
    console.log("\n");
    console.log("No transaction to sign. Waiting...");
    time += _timer * 60 * 1000;
    }
    else
    {
    var i;
    var dataHash = [];
    for (i = 0; i < totalUnconfirmed; i++) //We batch tx
    {
    //Get inner hashes
    dataHash[i] = obj.transactions[i].innerHash.data;
    console.log("Transaction hash:");
    console.log(dataHash[i]);

    //parsing Json to get the amount for daily amount
    dailyAmount += obj.transactions[i].inner.amount/1000000;
    console.log("Dayli amount:");
    console.log(dailyAmount);

    //parsing Json to get the amount
    amount[i] = obj.transactions[i].inner.amount/1000000;
    console.log("Transaction amount:");
    console.log(amount[i]);

    //MultisigSignatureRequest
    var transac = [];
    transac[i] = {
    wallet: _wallet,
    password: _password,
    account: _cosignatoryAccount,
    multisigAddress: _multisigAddress,
    innerHash: {
    data: dataHash[i]
    },
    hoursDue: _hoursDue,
    fee: _fee
    };

    //output a pretty formated JSON text
    var toPrettyJson = function(transac) {
    var e = JSON.stringify(transac,null,4);
    console.log(e);
    };

    //Maximal Amount is _maxAmount XEM per tx
    if (amount[i] > _maxAmount)
    {
    console.log("There is a problem, only n XEMs transaction max allowed !");
    console.log("Following Transaction cause problems:");
    console.log(dataHash[i]);
    throw new Error('Something goes wrong !'); //In this case we stop cosigning
    }
    //Maximum dayliAmount is _maxDayliAmount XEM
    else if (dailyAmount < _maxDayliAmount)
    {
    //Sign transaction
    nem.nccPost('/wallet/account/signature/send',transac[i]
    ,function(err) {
    console.log(err);
    }
    ,toPrettyJson
    );

    //Batch report
    console.log("Total transactions: ");
    console.log(totalUnconfirmed);
    console.log("Total amount: ");
    console.log(dayliAmount);
    time += _timer * 60 * 1000;
    console.log("Done, waiting...");
    }
    else{
    console.log("MAXIMAL AMOUNT REACHED !");
    time += _timer * 60 * 1000;
    }
    } //endFor
    } //endElse
    }; //end XEMsign function

    //Get unconfirmed transactions from local NCC
    nem.nccPost('/account/transactions/unconfirmed',data
    ,function(err) {
    console.log(err);
    }
    ,XEMsign
    );
    }, the_interval);
    });
    }

This program is only destined to sign transactions. It does not initiate them. You can adapt this script to communicate with a MYSQL database or else, you'll need to change the MultisigSignatureRequest view for TransferSendRequest and deal with pool database connection instead of inner hashes.
I'll explain it in part II.

To check the code on Github: https://github.com/QuantumMechanics/XEMsign

Still needs improvements but I'm actually using it (a bit modified) to automate signatures initiated from the XEM faucet and it works great.

It's really easy to quickly build such programs for any kind of projects, for example, I'm using NIS API for email notifications on incoming transactions, it's coded in PHP and with remote NIS it runs on a shared hosting plan.

Blog Logo

A Nember


Published


Image

NEM

Official Blog of NEM/XEM

Back to Overview