Multisignature Addresses and Omni


The recommended way to do a managed smart property issuance is through a multisignature address. Also, long-term storage of assets held on an institutional level, and institutional operations such as payroll management, may also benefit from using multisignature addresses. Most importantly, exchanges integrating the Omni Layer protocol can manage risk with one central multisignature address, or preferably, a series of rolled multisignature addresses.

The protocol does not currently support sending to multiple addresses in a single transaction, this is a feature we’d like to develop. Creating transactions that fulfill customer withdrawals as secondary outputs is not yet possible, so a combination strategy of hot wallets and multisignature repository wallets is recommended for doing a large number of send transactions, for now. For example, an exchange could keep 10% of the assets in a hot wallet that the server controls for fast withdrawals and then re-fill it on a daily basis.

It’s also possible to create applications that make the below illustrated process much easier, and even facilitate exciting uses like market places for real goods, with paid arbitrators available to settle disputes with a 3rd key.

First, one must safely generate and store private keys and their corresponding public keys. The best way to do this is install Linux on a formatted USB, drop a download of Omnicore on the same USB, wipe a hard drive, and boot from the USB. Load up Omnicore (or Bitcoin Core, for this purpose, it’s the same) and go to Help -> Debug -> Console. Then type:


Copy the address and paste it in front of the following command:

validateaddress <address>

The pubkey will be displayed, write it down, it’s in Hex (base 16) so your hand might get a cramp. Pasting it into an email and having a copy in the cloud is acceptable since pubkeys are not a security vulnerability, and are actually meant to be shared. The other board members or team members, or your accountant, or your spouse, or whomever is going to follow these steps on their own to generate their own private key, will have to share their pubkeys with you in order to generate the multisig address.

Now the very important part:

dumpprivkey <address>

Like it says on the tin, this will dump the private key for that address (though it only works if you’ve generated the address locally and it’s saved in that local client).

It’s very important that the privkey is written down on paper, make sure your hand-writing isn’t too bad, that characters that might be confused for another are marked.

Now it’s time to generate the multisignature address, let’s say you want to create a 2-of-3 address, you’ll do this by specifying the number of signatures needed (2) and listing an array of pubkeys:

 omnicore-cli createmultisigaddress 2 '["0232b7c756b51c8ad37fa63929d119b118af54598c8a0f1ab8f1966704e4cbff96",

The createmultisigaddress call will return the new address and the redeem script, which like the pubkey, is very long, annoying to physically write down, and not a security risk to share, but necessary to complete transactions. It can be copied to a digital back-up and shared in emails.

It’s essential that you fund the new address you just created with some bitcoin, it can be a very small amount, but no less than .005 is recommended. Check your transaction hash when you send, you’ll need it later.

Now the most technically complex part: preparing a transaction to be signed. There are a series of RPC calls that begin with “omni_createpayload_”and then are followed by the name of an existing transaction, for example “omni_createpayload_simplesend”, “omni_createpayload_trade”, and “omni_createpayload_issuancemanaged” – the full list can be found here.

Issuance of a managed smart property is one of the more parameter-intensive transaction types, here is a list:


Name Type Presence Description
ecosystem number required the ecosystem to create the tokens in (1 for main ecosystem, 2 for test ecosystem)
type number required the type of the tokens to create: (1 for indivisible tokens, 2 for divisible tokens)
previousid number required an identifier of a predecessor token (use 0 for new tokens)
category string required a category for the new tokens (can be “”)
subcategory string required a subcategory for the new tokens (can be “”)
name string required the name of the new tokens to create
url string required an URL for further information about the new tokens (can be “”)
data string required a description for the new tokens (can be “”)

omnicore-cli omni_createpayload_issuancemanaged 1 2 0 “” “” “myTokens” “” “Experimental multisig tokens.”

Very important note, OP_Return only allows 80 bytes of data, 4 of which are consumed by the omni market, so your hex payload must be less than 152 characters long (76 bytes), the categories parameters are not worth the text, just name your asset, include the URL and come up with a very succinct 3-5 word description. Longer strings will fail to modify the transaction.

The resulting hex is what would go into the OP_Return of a Bitcoin protocol transaction that would effectively make it an Omni Layer transaction.

We must also create the components of a bitcoin transaction as if we weren’t creating an Omni transaction, with an added bonus of inserting the payload you just create as an OP_Return, and after that we’ll have a proper transaction hex to sign.

The steps:

omni_createrawtx_input, then add the payload via
omni_createrawtx_opreturn, then add the reference  address via
omni_createrawtx_reference, and finally add the change address with

Now step by step with parameters:

omnicore-cli “omni_createrawtx_input” “<raw tx hex to modify, we’re creating a new one, but it’s necessary to put in the quotes and leave it blank>” “<the transaction id of the bitcoin you sent to fund the address earlier>” <the vout position of the output that this input transaction is providing to fund mining fees – an integer, usually 0 or 1, you can tell what integer by looking at the transaction on, the 0 position output is listed fist, then the 1 output, then the 2 output and so on>

Now copy the hex that was just emitted, as well as the Omni Layer transaction payload you created two steps ago, as parameters for the next step:

omnicore-cli “omni_createrawtx_opreturn” “<modified transaction you just got from the createrawtx_input>” “<hash that is the payload of the Omni transaction you want to put into the OP_return>”

You’ll get another modified hex representing the new data, now copy that and your multisig address as parameters:

omnicore-cli “omni_createrawtx_reference” “<the most recent transaction hex you want to build-up>” “<address to send to>”

Note that your reference address is only the address you’re sending from if you’re doing an issuance, usually this is the address you want to send tokens to. If you’re doing a grant or issuance, this step can be omitted.

Now finally, we’ll take the resulting transaction hex and add a change address (change as in “spare some change?” not as in “we want change”), which will also be the address you’re transacting from, so any bitcoin not used in the fee will end up on the same address.

omnicore-cli “omni_createrawtx_change” “<latest version of the hex for this transaction you’re building>” “[{\’txid\’: \'<id of input transaction that funded the address>\’,\’vout\’:<0 if the input you’re spending is first among inputs, 1 if second, ect. – an integer>,\’scriptPubKey\’:\'<3039a5ee506a381271c1a8974eda16457838f249 – should look like this, click the “Scripts and Coinbase” link under “Inputs and Output”in the tx view of and scroll down to the hex ouput corresponding to the output you’ve chosen to spend>\’,\’value\’:<BTC spent in output, an 8 decimal place floating point number, must equal the whole output chosen by vout from the input tx>\’},<other JSON objects representing other input transactions used, where applicable>]” “<your address to receive the change, same as in the previous action>” 0.00035

The last parameter is an integer for the miner fee paid.

One tricky thing about this step the JSON has to have “\” in front of every ” or ‘ used in the JSON part, but other JSON sections in, for example, the signrawtransaction call, don’t require this. Just a quirk of the design of the RPCs. If you don’t use the \’s you’ll get an “Error parsing JSON:” response.

Here’s a really clear example:

 ./omnicore-cli omni_createrawtx_change “0100000001cde12afc42d31cf0dc70ffdda31f11d470d45407790fc487e478223a249228fa0000000000ffffffff031ef579120000000017a314a6eb127dfb988196197f8e2aa1353974b66a2s648700000000000000001b6a14ef7d6e69000000000000007600000006c7c12e10aa0a0000000000001976a0b3e8cab9b8383e121f2dbf77be8fc79″[{\”txid\”: \”b13e92243a22783485c407780724d170d313cfa3ddff70dcf01cd342fc2ae1c\”,\”vout\”:0,\”scriptPubKey\”: \”b264a9ed243dfb686126127e8d1cb1356281d26a278487\”,\”value \”:0.749}]” “3QSYoxGXw5sfHLzMMSsDUgdW45G2W7M8sM2” 0.00025

There are extra spaces separating : and \ so the blog doesn’t show a :\ emoticon, remove those in practice.

And now we have a valid transaction to broadcast. Share the hex with your co-signers. They can copy it in along with their private keys using this command:

$ omnicore-cli signrawtransaction "0100000001e006a9...f16c9f4af17f8700000000" 

These parameters are: the transaction hex, a JSON including similar information as the change address call above, but including the redeem script shown when you generated the multisig (which can be saved digitally, as its compromise can’t really be used to compromise the address, but which is necessary to sign transactions on a multisig address). And finally, the private key. Note that the \’ required above is not required for the JSON here, just one of those things. Finally, use the pubkey prefixed with OP_HASH160 bytes and suffixed with OP_EQUAL byte.  This is the script to redeem that output and is what you use in the transaction. You can get it by decoding the transaction:

 ./omnicore-cli decoderawtransaction "0100000001cde12afc42d31cf0dc70ffdda31f11d470d45407790fc487e478223a249228fa0000000000ffffffff031ef558160000000017a914a9eb247dfb988196197f8e2aa1353974b66a2784870000000000000000166a146f6d6e69000000000000007600000006c7c12e10aa0a0000000000001976a914e8fab7b8383e121f2dbf77be8fc79effec252e7688ac00000000"
 "txid" : "5bef1c94f072317bc14201273eaf1f17a60ca4c97d5586b35333dc7b327b05c6",
 "version" : 1,
 "locktime" : 0,
 "vin" : [
 "txid" : "b13e92243a22783485c407780724d170d313cfa3ddff70dcf01cd342fc2ae1cd",
 "vout" : 0,
 "scriptSig" : {
 "asm" : "",
 "hex" : ""
 "sequence" : 4294967295
 "vout" : [
 "value" : 0.749,
 "n" : 0,
 "scriptPubKey" : {
 "asm" : "OP_HASH160 a9eb247dfb988196197f8e2aa1353974b66a2784 OP_EQUAL",
---> "hex" : "b264a9ed243dfb686126127e8d1cb1356281d26a278487",
 "reqSigs" : 1,
 "type" : "scripthash",
 "addresses" : [
 "value" : 0.00000000,
 "n" : 1,
 "scriptPubKey" : {
 "asm" : "OP_RETURN 6f6d6e69000000000000007600000006c7c12e10",
 "hex" : "6a146f6d6e69000000000000007600000006c7c12e10",
 "type" : "nulldata"
 "value" : 0.00002730,
 "n" : 2,
 "scriptPubKey" : {
 "asm" : "OP_DUP OP_HASH160 e8fab7b8383e121f2dbf77be8fc79effec252e76 OP_EQUALVERIFY OP_CHECKSIG",
 "hex" : "62b63ee8fab7b8383e121f2dbf77be8fe19effec252e7688ac",
 "reqSigs" : 1,
 "type" : "pubkeyhash",
 "addresses" : [

That arrow in the middle of the above block quote shows you where to find the hex you need to properly sign. The one at the bottom is what you’d need to use to sign a successor transaction that takes the output you’re about to produce as an input.

The result will be transaction you can copy and email to your other co-signers to continue signing. You’ll get a message suggesting an error and see “complete: false” but don’t let this fool, you, that means you did it right. Once the transaction has enough signatures, it will show an output where “complete:true”.

But before you sign that last transaction, make sure it’s something you’d want to sign with a special version of the decode call:

./omnicore-cli omni_decodetransaction “0100000001cde12afc42d31cf0dc70ffdda31f11d470d45407790fc487e478223a249228fa0000000000ffffffff031ef579120000000017a314a6eb127dfb988196197f8e2aa1353974b66a2s648700000000000000001b6a14ef7d6e69000000000000007600000006c7c12e10aa0a0000000000001976a0b3e8cab9b8383e121f2dbf77be8fc79effec252e7688ac00000000”

“txid” : “8cda8e91f973717b317426292d8c1637360c12c97d5586b35333dc7b327b05c6”,
“fee” : “0.00025000”,
“sendingaddress” : “3QSYoxGXw5sfHLzMMSsDUgdW45G2W7M8sM2”,
“referenceaddress” : “1J2P7J6yAkPOpVsanJSHx4RrhZqd1fc8dn”,
“ismine” : false,
“version” : 0,
“type_int” : 0,
“type” : “Simple Send”,
“propertyid” : 118,
“divisible” : true,
“amount” : “291.21130000”,
“confirmations” : 0

Once you’re sure you’re good on signing, follow the steps linked above and when complete:true is the result, you can take the resulting hex and do this:

omnicore-cli sendrawtransaction  “<completed, signed transaction hex>”

If you get an “insufficient priority” error, this is because your miner’s fee is not large enough.

Also note, the Bitcoin Core RPC “createrawtransaction” will make your fee the different between the input and the output, it doesn’t have all the steps involved here so by default just that call won’t include a change address, so if you try to send 1 bitcoin from an address containing 10, you’ll pay a miner’s fee of 9 BTC and have a bad time.


The output from sending the raw transaction, assuming no errors occur due to lack of sufficient signatures or fee, will be a transaction id that you’re used to seeing, copy it into a and or and you’ll see the transaction out in the wild, pending confirmation.

For periodic business operations, weekly or monthly, this is a somewhat tedious process, but once you get the hang of it and have the commands in your SSH history, it’ll be easy. Except, for security reasons you must scrub your bash history after using a private key in the signing transaction, more on that here. It’s considered good security practice to create a new multisignature address prior to using your private keys on the old one, and periodically port funds and control of a smart property to it, limiting the possibility of a compromise over time for a highly re-used set of private keys.

Just to spell it all out, here’s that transaction:


Change the issuer on record of the given tokens.


Name Type Presence Description
fromaddress string required the address associated with the tokens
toaddress string required the address to transfer administrative control to
propertyid number required the identifier of the tokens


"hash"  // (string) the hex-encoded transaction hash


$ omnicore-cli "omni_sendchangeissuer" \
    "1ARjWDkZ7kT9fwjPrjcQyvbXDkEySzKHwu" "3HTHRxu3aSDV4deakjC7VmsiUp7c6dfbvs" 3

It’s possible for co-signers to write down a private key (and it’s important that they do it well, without ambiguity of upper or lower case in their long-hand, maybe a typewriter would be useful) and rarely use it, and for the administrator to use those pubkeys over and over while generating a new key and address each operations cycle.

It’s worth it to take more time generating keys in an air-gap and signing transactions in the same environment, reduces the risk profile to a physical compromise only. Every layer involved is a potential attack vector, which is why the best way to sign a transaction is in an unsync’ed Omnicore QT instance (Help->Debug->Console) on a Linux device that is clean-booted from a recently formatted USB stick and never used to access the internet.

People talk about air-gaping like it’s hard, but the hardest part is simply allocating a device for it, which should be a trivial line item for a serious operation. Different organizations will find different balances of compromise. Probably the best trade-off would be having a script that takes one private key as a parameter, pasted in fresh before the script runs, to prepare and partially-sign the relevant transactions and output a JSON object listing them along with their decoded parameters, and have it auto-email the CFO or accountant, who then saves it to a .txt file, carries it to the air-gap device on a USB stick, and then copies the long transaction hexes in while signing them manually. Maybe manually decode them again before signing to check for possible man-in-the-middle attacks. Then take a .txt file of those signed transactions, or maybe a .json file, bring it over to the internet-connected computer on the USB stick, and either email it back or submit it to a server to run another script that tries a sendrawtransaction call for each transaction. Hacking your operation should be as challenging as the famous sequence in the first Mission:Impossible film.

No exchange that requires multiple manual signatures to move assets has yet been hacked.

If you’re new to server management, see this tutorial.

Multisignature Addresses and Omni

One thought on “Multisignature Addresses and Omni

Comments are closed.