Multisignature transactions with notary subsystem

Multisigning in accounts and transactions

First off, we must notice that ”multisigning” can be referred to in two different contexts: one is about signatures for M-out-of-N keys accounts and another one is about a number of signers in a transaction (see our previous post on signers and scopes). These are quite different cases, we’ll use ”multisignature account” term for accounts corresponding to verification scripts with a number of keys and ”multisigned transactions” for transactions having more than one signer.

Multisigned transaction
INDEX    OPCODE       PARAMETER                                                             
1 PUSHDATA1 02103a7f7dd016558597f7960d27c516a4394fd968...
36 PUSHDATA1 02a7bc55fe8684e0119768d104ba30795bdcc86619...
71 PUSHDATA1 02b3622bf4017bdfe317c58aed5f4c753f206b7db8...
106 PUSHDATA1 03d90c07df63e690ce77912e10ab51acc944b66860...
141 PUSH4
142 SYSCALL System.Crypto.CheckMultisig (9ed0dc3a)
INDEX    OPCODE       PARAMETER                                                                                                                           
0 PUSHDATA1 199bdd216a4c6c4548fa04c2e75a20cbcbcc85baecd...
66 PUSHDATA1 b17f98a9649938a035caf57dda7eb5d169f80620df2...
132 PUSHDATA1 6d5d1157541c033ce9ede5fe16893ca27b2154dd823...

State of multisigning

Consensus and state validation

dBFT consensus is at the core of Neo, this is how Neo blocks get created and signed. Consensus nodes communicate over P2P network with messages packed into ExtensiblePayload. When a node sends a Commit message for proposed block, it includes a signature for this block made using node’s key. Messages are broadcasted through the network and eventually every other node gets enough commits to finish the block. Then, nodes can create an invocation script using these signatures and the block is ready (similar scheme is used by state validation nodes for their job).

Block signature collection over P2P
  • blocks
  • transactions
  • extensible payloads


We have a committee that from time to time does wonders, like reducing fees on mainnet; and it does them with transactions that use a special committee multisignature account. To collect appropriate number of signatures committee members use out of band communication channels (chats of various sorts), no one can see the transaction on the Neo P2P network until it’s signed. An incomplete (not fully signed) transaction is created by one of the committee members; then, he passes it to the next committee member who signs it and passes this new incomplete context to the next one. It all repeats until enough signatures are collected.

Committee out of band signing

Oracle nodes

Oracle responses are created by a group of oracle nodes with (on current mainnet) three-out-of-four multisignature account. The process is completely automated of course, so it uses a quite different technique that at the same time has one thing in common with committee way of signing — it’s out of band, not using P2P network.

Oracle signature exchange

Centralized collection

Oracle signature exchange scheme can be simplified by using some centralized entity that every node would connect to. But this would mean that either every application creates a service like that for its purposes, or someone has to run this generic service available for anybody. Running a service is not free both in terms of human resources and hardware cost, especially given the fact that this service would be a natural point of interest for attackers, so it’s not likely to happen as something available for everyone to use.

Centralized signature collection

On-chain collection

Another natural approach used by many other blockchains is just managing signatures in a contract. This can also be done in Neo, and in fact NeoFS team has a lot of experience with this because that’s the way NeoFS contracts orignally worked. The idea is rather simple: contract keeps a set of authorized keys in its storage and then some method requiring M-out-of-N signatures is invoked by each signer in a separate transaction. Contract’s code checks if it has enough signatures on invocation. If that’s the case, it does the action required; if not it just increments the counter of signatures received and exits. In a pseudo-code it looks like this:

keys = getTrustedKeys() 
for k in keys {
if runtime.CheckWitness(k) {
signerKey = k break
if signerKey is null {
// return error
storage.Put(requestID+signerKey, "")
if countSigners(requestID) < m {
return // Wait for more signatures.
// Do the action.

Notary subsystem

Notary subsystem is a proposal for integration into future Neo releases. It’s implemented in NeoGo as an extension since version 0.93.0 (it’s even documented), stabilized and improved in various ways since then. The protocol is described in a GitHub issue and it consists of several key components:

  • native contract
  • P2P payload with a separate pool on every node
  • node module (to be used by designated nodes)
  • additional transaction attributes: Conflicts, Not Valid Before and Notary Assisted
Example notary payload
Notary signature collection over P2P

Sponsored transactions

Unlike with out-of-band solutions, notary payloads can easily be observed on the network as they’re spreading through it. NeoGo has implemented an additional logic in its subscription mechanism for external applications to be able to get an event when a new payload is added into the pool. This then allows to react on such events: add some missing signature and make the transaction valid.


Notary subsystem is still a proposal for the core protocol, but it’s already implemented in NeoGo and you can try it out. We’ve been using it successfully in NeoFS for more than nine months by now, and it greatly simplified both contracts and backend logic, making the system more reliable and performant.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store