ION Multisig Wallets

Lord Dark Helmet
14 min readMar 5, 2021

ION multiple signature wallets (multisig), allow multiple parties to agree on coin distribution. This is a safer than relying on a single person.

For more information on ION and the ionomy ecosystem please visit:
https://ionomy.com/

First some basics. A multisig wallet is a wallet that needs more than one signature to operate. When you are using your own personal wallet, you only need yourself to send funds. However, in a multisig wallet, more than one party needs to sign off on the transaction to make it happen. You can setup your multisig wallet in a variety of ways, from requiring everyone to sign or just a few parties like 2 of 3, 9 of 9, or even 2 of 10. We will look into different strategies later on in the article, but for simplicity we are going to use 2 of 3, which is the most common format.

Multisig Wallet Basic Operational Story

A multi-signature wallet that requires 2 of 3 signatures is the most common. I am going to give each of these signatures a label, Buyer, Seller, and Arbiter. In our example, the buyer and seller do not trust each other. The Arbiter is a third party that the buyer and seller have approved of. The Arbiter is not in collusion with the Buyer or Seller. The Buyer wants to buy a real world product from the Seller. To facilitate this transaction a multi-signature (multisig) wallet is created, the Buyer, Seller, and Arbiter are all signature holders. The wallet is created in such a way that 2 of 3 signatures are required to move funds from the wallet.

The Seller does not trust the Buyer, so the seller wants the Buyer to put the money up front. The Buyer is ok with sending money to the multisig wallet address because the Seller cannot take it without either the Buyer or Arbiter approving the transaction (movement of funds).

The Seller Never Delivers the Goods

In this case the Seller does not complete their part of the agreement. The Buyer sent funds to the multisig address, but never got the goods from the Seller. The Buyer creates a transaction to get the funds back from the multisig address, and the Arbiter signs it. Because the transaction had the 2 of 3 required signatures, the transaction is approved, funds move from the multisig address to the Buyers’s address.

The Buyer Never Sends the Funds

In this case the Seller sees that the Buyer did not send the appropriate funds to the multisig address. So the seller never delivers the goods to the Buyer.

The Seller Delivers the Goods, but the Arbiter is Missing in Action

In this case the Seller will create a transaction to move Funds from the multisig address to the Sellers address. Because the multi-sig address needs 2 of 3 signatures, the Buyer can sign the transaction that moves the funds to the Sellers address. No Arbiter required!

Everything Goes as Planned

In this case the Buyer funds the multisig address. The Seller delivers the goods. The Seller creates a transaction that will move funds from the multisig address to the Seller’s Address. The Arbiter gets conformation from the Buyer that the goods were delivered and signs the transaction, meeting the 2 of 3 requirement, and the funds are sent from the multisig address to the Seller’s address.

How Do I Setup an ION Multisig Address?

For this example I am going to be using the Windows 10 based QT wallet. This is the most popular method right now. If you are using the command line or are on a deferent OS, the commands will be the same, it just may look a little bit different. For this example we are going to use a multisig address with 3 parties Buyer, Seller, and Arbiter. 2 of 3 of these parties are required to sign any transaction.

Here are the basic steps:

  • Create New Addresses for Each Signing Party
  • Get the pubkey (Public Key) for Each Signing Party Address
  • Create the Multisig Address
  • Fund the Multisig Address
  • Create a Transaction that Distributes Funds
  • Sign the Transaction to Distribute Funds
  • Send the Transaction to Distribute Funds

Step 1: Create New Addresses for Each Signing Party

The first step is for each party to create an address that they will use to sign the transactions. I highly recommend that a new address is used and that no funds are kept in the address.

Each party will create their own address.

The Buyer’s Address

For example, the buyer decides that he wants to create their address using the Windows Based GUI QT wallet. He does this by pressing File -> Receiving Addresses…

Then click the “New” button

Now we give the address a label. In this case I am using “Buyer” as my label.

Now you can see that the “Buyer” address has been created, and the public address is:
ibwcn3cAe2kpKZvxQ7sz8q6KBkjeUN5PKN

The Seller’s Address

The seller also needs an address. The seller decides to use the debug console to create the address. In this case the seller presses Tools -> Debug console.

In the debug console we are going to use the getnewaddress command to generate a new address.

The wallet generates a new address:
ibVxXKmhVoDFwKV3n8pArKpKsWATbYy9MY
In our example, this will be the Seller’s address. Although not mandatory, I suggest that you give the address an appropriate label. In my case I called it “Seller”.

The Arbiter’s Address

To be brief, the Arbiter’s address is:
ip3sKCpLQPPR4UVLKMssAuQdZShhSqrZun

Step 2: Get the pubkey (Public Key) for Each Signing Party Address

Now that each party has a new address, we need to share the pubkey with each member. While one party can create a multi-signature address, you want to make sure all parties validate the address. This would prevent one party from creating a fake multisig address where they control 2 of the 3 needed signatures.

While you want to avoid exposing your pubkey to everyone, it cannot be used to access your funds. REMEMBER TO NEVER DISCLOSE YOUR PRIVATE KEY.

To get your pubkey we are going to use the validateaddress command. The command is simple to use, just add your address.

validateaddress YourAddressGoesHere

Buyer

Using the Debug colsole, the buyer is going to use the command using the buyer’s address

validateaddress ibwcn3cAe2kpKZvxQ7sz8q6KBkjeUN5PKN

From the debug console, you can see the pubkey for the Buyer.
0384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c

If you type in someone else’s address, you will not get the public key. For example if the Buyer tried to use the Seller’s address the following would show up. Notice that there is no pubkey.

Seller

The seller runs the command:

validateaddress ibVxXKmhVoDFwKV3n8pArKpKsWATbYy9MY

The resulting pubkey for the Seller is:
02f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd

Arbiter

The Arbiter’s pubkey is:
0345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b

Step 3: Create the Multisig Address

The pubkey for each address needs to be distributed to each party member. Any member can create the multisig address. I encourage each signature holder for the multisig address, validate address using each parties pubkey.

The createmultisig command is used to create the multisig address.

The createmultisig command has two parts that need to be specified.

  1. The first part is the number of signatures required, in our case it is two signatures.
  2. The second part is the JSON array of keys for each pubkey. Wait… the what???

Let’s talk about this JSON thing. This is just a way of adding the pubkey of the Buyer, Seller, and Arbiter. In our case it is a JSON string containing each pubkey. In our case the string will look like this:

[ "0384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c", "02f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd", "0345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b" ]

This is the [ “Buyer”, “Seller”, “Arbiter”] pubkeys. The order of pubkeys is important and must be agreed upon by all participants. If one party uses a deferent order, they will get a different multisig address.

Our command has this format:
createmultisig #_of_Required_Signatures '[ “Buyer”, “Seller”, “Arbiter”]'

Nte the “'” symbols surrounding the JSON string. The ' symbol acts like quotation marks to make sure the command is properly read by the computer.

Here is the command we are going to use:

createmultisig   2   '[ "0384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c", "02f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd", "0345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b" ]'

Again, notice the ' symbols surrounding the JSON. The ' symbols are needed for the program to properly read the contents of the JSON string.

Great at this point we have two items, address and redeemScript

  • address is the location of the funds. This is where the Buyer will send funds.
  • redeemScript is needed later to spend the funds.

So we have our multisig address!:
cWaTxhqymc7frunaMhXhoUaeBcTHzrcY4d

Let’s also keep track of our value:

52210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53ae

Step 4: Fund the Multisig Address

For our example, the multisig address, cWaTxhqymc7frunaMhXhoUaeBcTHzrcY4, is going to be funded by the buyer using the Windows 10 QT GUI wallet.

If we look at an explorer, we will see that our funds have arrived at the address. It is impossible for the buyer to get back the funds unless at least two parties out of three approve.

Here is what the explorer looked like at the time of the transaction:

The address , cWaTxhqymc7frunaMhXhoUaeBcTHzrcY4,has received 10 ION.

Step 5: Create a Transaction that Distributes Funds

At this point the buyer has sent the funds to escrow. The Seller can see that the funds have been sent to the address. The seller has also verified the legitimacy of the address by generating the address as per Step 3.

Any party can create the transaction that everyone is going to sign, even a third party who is not a signature holder! I am not part of this transaction, but I am going to create the transaction. I do this by using the createrawtransaction command and looking up some information.

createrawtransaction has two required attributes. The first is how it is being funded, and the second is where the funds are going.

How it is being funded is by transactions. If you pull back the curtain a bit on crypto currencies, you will see that coins are really stored in the transactions, not the addresses. So we are going to look up the transaction of our 10 ION in the multisig address. Using our block explorer we can see that the transaction (TXID)is: 826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9
we can also see that the 10 ION was output index 0.

You can do this within the wallet. You can use the following command to produce a JSON with all of the needed data

getrawtransaction "Your Trasaction ID" true

I am showing the explorer method now, and will show the in wallet method later in the article.

So part 1 is a JSON in this format:

[{"txid":"YourTXID","vout":TheOutputIndex}]

In our case it is

[{"txid":"826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9","vout":0}]

The second part is easy, it is a JSON that contains where the funds are going to go and how much we are going to send. It has this format:

{"DestinationAddress":HowMuchToSend}

In our case we are sending 9.9 ION to the Seller’s Public Address ibVxXKmhVoDFwKV3n8pArKpKsWATbYy9MY:

{"ibVxXKmhVoDFwKV3n8pArKpKsWATbYy9MY":9.9}

Why are we sending 9.9 instead of 10?
We are sending less than the total amount in this transaction to ensure that we have enough ION to cover the transaction fees. Please note that tx fees are needed so plan accordingly. You will notice that I paid a tx fee of 0.1 which is very high! When you do not specify, the tx fee will be the balance! You have been warned.

When we put the command together we need to remember to wrap each JSON in its own single quotation marks so the command can be properly read.

createrawtransaction '[{"txid":"826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9","vout":0}]' '{"ibVxXKmhVoDFwKV3n8pArKpKsWATbYy9MY":9.9}'

The result is a big long string.

010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b820000000000ffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000

The string is the hex encoded raw instructions to perform the transaction. Unfortunately it is useless unless it has enough signatures. Right now it has zero signatures.

Step 6: Sign the Transaction to Distribute Funds

Now that we have our transaction instructions, we need to start signing

Any eligible party can sign first, in this case the Seller is going to sign first.

The seller uses the signrawtransaction command to sign the transaction. The command has a few parts, the first part is the raw hex encoded transaction instructions.

010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b820000000000ffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000

The second part is the prevtxs This is a JSON array of previous dependent transaction outputs. In our case the transaction that funded the multisig address.

[ (json array of json objects, or ‘null’ if none provided)
{
"txid":"id", (string, required) The transaction id
"vout":n, (numeric, required) The output number
"scriptPubKey": "hex", (string, required) script key
"redeemScript": "hex", (string, required for P2SH) redeem script
"amount": value (numeric, required) The amount spent
}
,…
]

OK, that is a lot of items. We already know a few. The txid is 826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9 , we know this from the explorer. We also know vout is 0 from the explorer. scriptPubKey sounds like something we have not seen before let’s find it. While it is visible in the explorer on the “raw transaction” tab, I am going to show you how to find it in within the wallet in the Finding the scriptPubKey excerpt below. In our case,
scriptPubKey= a9143fff3890db19d1e7c0f75e7f660213047de9abc287
The redeemScript value is

52210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53ae 

Finally we need the amount of ION. In this case it is 10 ION. When we put this in, we want whole ION and not sat value, so be sure to represent it at 10.0 , If you just use 10 it will be in sat.

Putting it together the JSON looks like this:

[ 
{
"txid":"826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9",
"vout":0,
"scriptPubKey": "a9143fff3890db19d1e7c0f75e7f660213047de9abc287",
"redeemScript": "52210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53ae",
"amount": 10.0
}
]

Finding the scriptPubKey
We can get this value by looking at the transaction in verbose mode. Use this command:
getrawtransaction 826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9 true
The output looks like this:

What we see here is our output with 10 ION. Underlined in orange, we see the scriptPubKey, inside of that we see the HEX value for the scriptPubKey, this is what we are going to use.

The third and final part is the private key. NEVER SHARE YOUR PRIVATE KEY. DO NOT SEND SCREENSHOTS. IF YOU SHARE THIS IN ANY WAY YOUR FUNDS CAN AND WILL BE TAKEN FROM YOU! To get your private key use the following command: dumpprivkey “Your Public Address” In our case the Seller will use the Seller’s public address:

dumpprivkey “ibVxXKmhVoDFwKV3n8pArKpKsWATbYy9MY”

The Seller’s private key is: PhmDwnTNjPnNrHcCArRSPo9skAVXjmJVmBRWrsxYbc2Dn5RrMSjH

OK we now have the three parts to the signrawtransaction command. We have the raw hex encoded transaction instructions

010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b820000000000ffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000

We have the prevtxs JSON array

[ 
{
"txid":"826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9",
"vout":0,
"scriptPubKey": "a9143fff3890db19d1e7c0f75e7f660213047de9abc287",
"redeemScript": "52210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53ae",
"amount": 10.0
}
]

and we have the private key in JSON format:

["PhmDwnTNjPnNrHcCArRSPo9skAVXjmJVmBRWrsxYbc2Dn5RrMSjH"]

To assemble the command we are going to put each of the three parts in single quotation marks to ensure that they are read in properly:

signrawtransaction '010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b820000000000ffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000'  '[ 
{
"txid":"826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9",
"vout":0,
"scriptPubKey": "a9143fff3890db19d1e7c0f75e7f660213047de9abc287",
"redeemScript": "52210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53ae",
"amount": 10.0
}
]' '["PhmDwnTNjPnNrHcCArRSPo9skAVXjmJVmBRWrsxYbc2Dn5RrMSjH"]'

The result looks like this:

Two interesting things to point out. First is the complete field is false. This means that we do not have enough signatures yet. You will also notice an error, “Operation not valid with the current stack size” in our case this is because we do not have enough signatures yet. What we will need going forward is the hex field

010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b8200000000b40047304402204c18c019b81595f708e5d665cdb473bcef163fb37f77b8babf69b575543d10d702206c05b1a3ccc456e48fa57d3e5fb4079e9ef5a83d1419f11cf15cbf03f6a9668f014c6952210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53aeffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000

This looks a lot like our raw hex encoded transaction instructions, and it is! The bold characters are new and are the Seller’s signature.

Now we need one more party to sign. In this case either the Buyer or the Arbiter can sign. For this example the Arbiter is going to sign.

We are still going to use the signrawtransaction command, but in this case we are going to use our updated raw hex encoded transaction instructions that now have the Seller’s signature. The prevtxs JSON array will remain the same. The Arbiter will also need to use the Arbiter’s own private key which I am not going to expose so it sill be hidden in this example.

The command looks like this:

signrawtransaction '010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b8200000000b40047304402204c18c019b81595f708e5d665cdb473bcef163fb37f77b8babf69b575543d10d702206c05b1a3ccc456e48fa57d3e5fb4079e9ef5a83d1419f11cf15cbf03f6a9668f014c6952210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53aeffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000'   '[ 
{
"txid":"826bf043fcfe04f946001d2928c691a562622fcddcc05fcba999af8bcb9ee3e9",
"vout":0,
"scriptPubKey": "a9143fff3890db19d1e7c0f75e7f660213047de9abc287",
"redeemScript": "52210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53ae",
"amount": 10.0
}
]' '["*******************Hidden*******************"]'

The first thing you will notice is that the complete field shows true. This means that the required number of signers have signed! Our raw hex encoded transaction instructions now have two signatures:

010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b8200000000fc0047304402204c18c019b81595f708e5d665cdb473bcef163fb37f77b8babf69b575543d10d702206c05b1a3ccc456e48fa57d3e5fb4079e9ef5a83d1419f11cf15cbf03f6a9668f0147304402205e99e048e9dd8d33146746715cf35985c7d7a1a68635ea5e6319ea860e2c24dc02206c0736a17501f8f6aa54aeb689d774998c5cdd41aae53921a3065791597b854f014c6952210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53aeffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000

Step 7: Send the Transaction to Distribute Funds

At this point any party can send the funs by using the sendrawtransaction and the raw hex encoded transaction instructions. The full command looks like this

sendrawtransaction 010000000000000001e9e39ecb8baf99a9cb5fc0dccd2f6262a591c628291d0046f904fefc43f06b8200000000fc0047304402204c18c019b81595f708e5d665cdb473bcef163fb37f77b8babf69b575543d10d702206c05b1a3ccc456e48fa57d3e5fb4079e9ef5a83d1419f11cf15cbf03f6a9668f0147304402205e99e048e9dd8d33146746715cf35985c7d7a1a68635ea5e6319ea860e2c24dc02206c0736a17501f8f6aa54aeb689d774998c5cdd41aae53921a3065791597b854f014c6952210384a81266cab841ce5a9f35a79810384f0425f63eb33683648c6e459b73b1f58c2102f2b5395c5f10bf9530eaeb07ebff175446977f8466fb0773e75c4ebc35a7cabd210345a2fd572e00f0a368b067f67cc3e0c1f5fe240a2ef8802b0195f2cf9e05658b53aeffffffff018033023b000000001976a9145f4962b15c6cdb0df455ae486cfb04284cf9f3b288ac00000000

The result is:
51d3bc735b624d03c45896a3718d525d14553dd82fdae0490f5f9d0966c55500

If we type that txid into the ION block explorer, we can see that we sent 9.9 ION to the Seller’s address.

Keep your private keys private

In this tutorial, I exposed the private keys for the the Seller. This is where the funds were sent. Your reward for reading this far is my full permission to take the funds sent to that address. This also serves as a warning to others that you should never expose your private keys to anyone for any reason. You will also notice that I only exposed 1 of the three private keys in the multi-signature address. This means that the funds in the multi-signature address are safe. You are welcome to try and get it, I added 11.999 ION to the multisig address. Coins cannot be moved unless two signature holders decide to sign.

This also brings up the danger of requiring too many signatures. Let’s say you run a company and you think it is a good idea to have all 7 members of the board sign off on transactions from your multisig address. In this case if just one board member looses the key the funds are inaccessible and lost forever. The right ratio of required signatures and number of signers will depend on what is best for your unique situation.

For this tutorial I used ION Core version 5.0.0

--

--