Understanding MultiSig on NEO

Igor Machado
NeoResearch
Published in
4 min readAug 7, 2018

--

Well, MultiSig is one of those things that many people hear about, understand the concept, but don’t know exactly how to implement on NEO. I have good news, it’s not that complicated, and can be very easy with some basic concepts in mind :)

To understand how multisig contracts (or multisig “addresses”), let’s take a look on how basic addresses work on NEO.

Basic Addresses

The most basic addresses on NEO (the ones you can get easily on every NEO wallet software) are built on top of the concept of private-public keys. As an example, consider this address: APLJBPhtRg2XLhtpxEHd6aRNL7YSLGH2ZL

What does it mean? First of all, each address is associated to a scripthash, a 20-byte array, which in this case is: 0f2b7a6ee34db32d9151c6028960ab2a8babea52 (you can use neocompiler.io “converter” tools to get this value easily)

So, every Address is just a base58 representation of a scripthash (plus checksum and NEO mark byte), which makes it much safer for usage than the pure scripthash (if some character is wrong or mispelled in Address, an error message can be displayed). The name “scripthash” is indeed very intuitive, because it is the hash (HASH256) of a script, which are the commands necessary to validate who are you.

Let’s build a simple Address. First, you need a private-public key pair. Take the public key 036245f426b4522e8a2901be6ccc1f71e37dc376726cc6665d80c5997e240568fb, so let’s build the following script: PUSH the 33 bytes of this public key on memory (opcode 21), and verify that the witness who signed the transaction has used the correct private key (opcode ac CHECKSIG). So, this is the result: 21 + 036245f426b4522e8a2901be6ccc1f71e37dc376726cc6665d80c5997e240568fb + ac = 21036245f426b4522e8a2901be6ccc1f71e37dc376726cc6665d80c5997e240568fbac

Calculate the scripthash of this script (using neocompiler.io tools), the result is: 0f2b7a6ee34db32d9151c6028960ab2a8babea52 (which is our well known address APLJBPhtRg2XLhtpxEHd6aRNL7YSLGH2ZL).

MultiSig Addresses

A MultiSig addresses is quite the same thing, the only difference is that you add multiple public keys (each one from a different person, for example), and you inform how many people should sign the transaction in order to be valid.

Suppose we want to create a multisig for THREE different persons (public keys):

  • 036245f426b4522e8a2901be6ccc1f71e37dc376726cc6665d80c5997e240568fb
  • 0303897394935bb5418b1c1c4cf35513e276c6bd313ddd1330f113ec3dc34fbd0d
  • 02e2baf21e36df2007189d05b9e682f4192a101dcdf07eed7d6313625a930874b4

We want to have at least TWO of them to sign the transactions, so we proceed this way:

  • push on stack value 2 (minimum number of signatures). opcode 52
  • push each of the three public keys (remember this order is important). opcode 21 (corresponds to 33 bytes)
  • push value 3 (total number of people). opcode 53
  • check that multisig is correct (opcode ae CHECKMULTISIG)

This is the result: 52 + 21 + 036…fb + 21 + 030…0d + 21+02e…b4 +53+ae

5221036245f426b4522e8a2901be6ccc1f71e37dc376726cc6665d80c5997e240568fb210303897394935bb5418b1c1c4cf35513e276c6bd313ddd1330f113ec3dc34fbd0d2102e2baf21e36df2007189d05b9e682f4192a101dcdf07eed7d6313625a930874b453ae

Calculate the scripthash (and Address): 4d0c0932fa032debdceaaf5cd8086cf3f882961f / AJetuB7TxUkSmRNjot1G7FL5dDpNHE6QLZ

Okay! Now, let’s test it using neocompiler.io :)

Testing the MultiSig

I transfered 10 NEO (privatenet) to this address:

10 NEO sent to AJetuB7TxUkSmRNjot1G7FL5dDpNHE6QLZ

Let’s transfer it back… now we need to build a transfer transaction. We will need a Verification script and an Invocation script. Verification script is exactly the script that will validate the transaction, which is our multisig address script:

5221036245f426b4522e8a2901be6ccc1f71e37dc376726cc6665d80c5997e240568fb210303897394935bb5418b1c1c4cf35513e276c6bd313ddd1330f113ec3dc34fbd0d2102e2baf21e36df2007189d05b9e682f4192a101dcdf07eed7d6313625a930874b453ae

First we build the transaction header (using neocompiler.io transaction build tools), and inform the transfer of 10 NEO, etc. Now we need to sign the transaction (at least with two different privatekeys). Let’s sign using address 1 and 3 on the list (not using number 2).

First privatekey is eaf5841d83a6776c003b549d556065d700f8e460b20435ae9223a568297daaf7 (corresponding to publickey 036…fb), and third privatekey is 22c41dde44c226b69244893bd8fd65fa5ea45e1e4fbd87e8605f962d3b9e6f57 (corresponding to publickey 02e…b4). (we are NOT using second privatekey, which is 04a70fe26f77cf39d9c01d4ec7847b8c7cf47012fbc07b4a4bf0989d05d604cd)

First signature is: ee9596a52a9033b1103f9a710467b1ac84575426c8a9a2a3c001cb04b2a5b08e266a19e3c216ed1ab8ae2c00b23b9e8ee8d9d8700958fb8655097d789dc990c9 (64 bytes, opcode 40)

Third signature is:

9060c6ce30864233dc96bbedaaf2c98fc5e12f673493b63ed0e6f83d760037e56347408cf8fae3cb2a3266a98aaccfcad10c7591a683b5701cf085e1f9c5aa4a (64 bytes, opcode 40)

So, now we build the Invocation Script, which just push first signature and third signature: 40 + ee95…c9 + 40 +9060…4a

40ee9596a52a9033b1103f9a710467b1ac84575426c8a9a2a3c001cb04b2a5b08e266a19e3c216ed1ab8ae2c00b23b9e8ee8d9d8700958fb8655097d789dc990c9409060c6ce30864233dc96bbedaaf2c98fc5e12f673493b63ed0e6f83d760037e56347408cf8fae3cb2a3266a98aaccfcad10c7591a683b5701cf085e1f9c5aa4a

Complete invocation to transfer 10 NEO from multisig address

Now we got the 10 NEO back ;)

Transfer from multisig
Invocation transaction operations that allowed to transfer from multisig

Final remarks

Ok, this is the process which allows is to create and manage multisig addresses, obviously that shouldn’t be made manually like we did! In a nice wallet software this operation could be made in a very transparent way, but the objective here was the learning ;)

When passing the signatures, remember you must follow the original order! For example, we have PUB1, PUB2 and PUB3, so we must pass at least two to get the transaction done. It could be SIGNATURE1 + SIGNATURE3 (what we did), or SIGNATURE1 + SIGNATURE2, or SIGNATURE2 + SIGNATURE3… but if you try to pass SIGNATURE2 + SIGNATURE1, that will (hopefully) not work. So, practice a lot, and good luck :)

=======================================

P.S.: If someone is crazy enough to take a look, this was the original sendrawtransaction that was performed:

{ “jsonrpc”: “2.0”, “id”: 5, “method”: “sendrawtransaction”, “params”: [“800000016f291e0c1f333d837b84fb707f2f0c91b3f25b6f8c4e397b1d20cc6758e4aed50000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ca9a3b0000000023ba2703c53263e8d6e522dc32203339dcd8eee9018240ee9596a52a9033b1103f9a710467b1ac84575426c8a9a2a3c001cb04b2a5b08e266a19e3c216ed1ab8ae2c00b23b9e8ee8d9d8700958fb8655097d789dc990c9409060c6ce30864233dc96bbedaaf2c98fc5e12f673493b63ed0e6f83d760037e56347408cf8fae3cb2a3266a98aaccfcad10c7591a683b5701cf085e1f9c5aa4a695221036245f426b4522e8a2901be6ccc1f71e37dc376726cc6665d80c5997e240568fb210303897394935bb5418b1c1c4cf35513e276c6bd313ddd1330f113ec3dc34fbd0d2102e2baf21e36df2007189d05b9e682f4192a101dcdf07eed7d6313625a930874b453ae”] }

--

--