Multisignature

Multisignature transactions are transactions which require multiple signatures before execution. In simplest example it means that, if there is token wallet which require 5 signatures from different parties, all 5 parties must sign spent transaction, before it will be processed.

It is standard functionality in blockchain systems and it is also implemented in IOTA.

Note

You can read more about IOTA multisignature on the wiki.

First, we will take a look on what Multisig API (s) you can use, and what PyOTA Multisignature Types are there for you if the standard API is not enough for your application and you want to take more control.

Starting from Generating multisignature address, a tutorial follows to show you how to use the multisignature API to execute multisig transfers. The complete source code for the tutorial can be found here.

Multisig API

The multisignature API builds on top of the extended API to add multisignature features. Just like for the regular APIs, there is both a synchronous and an asynchronous version of the multisignature API, however, as there is no networking required during the multisignature API calls, the difference between them is only how you can call them.

Synchronous Multisignature API Class

class iota.multisig.MultisigIota(adapter: Union[str, BaseAdapter], seed: Union[AnyStr, bytearray, TryteString, None] = None, devnet: bool = False, local_pow: bool = False)

Extends the IOTA API so that it can send multi-signature transactions. Synchronous API.

Caution

Make sure you understand how multisig works before attempting to use it. If you are not careful, you could easily compromise the security of your private keys, send IOTAs to unspendable addresses, etc.

Example Usage:

# Import API class
from iota.multisig import MultisigIota

# Declare a multisig API instance
api = MultisigIota(
        adapter = 'http://localhost:14265',

        seed =
            Seed(
                b'TESTVALUE9DONTUSEINPRODUCTION99999JYFRTI'
                b'WMKVVBAIEIYZDWLUVOYTZBKPKLLUMPDF9PPFLO9KT',
            ),
)

response = api.get_digests(...)

References:

Asynchronous Multisignature API Class

class iota.multisig.AsyncMultisigIota(adapter: Union[str, BaseAdapter], seed: Union[AnyStr, bytearray, TryteString, None] = None, devnet: bool = False, local_pow: bool = False)

Extends the IOTA API so that it can send multi-signature transactions. Asynchronous API.

Caution

Make sure you understand how multisig works before attempting to use it. If you are not careful, you could easily compromise the security of your private keys, send IOTAs to unspendable addresses, etc.

Example Usage:

# Import API class
from iota.multisig import AsyncMultisigIota

# Declare a multisig API instance
api = AsyncMultisigIota(
        adapter = 'http://localhost:14265',

        seed =
            Seed(
                b'TESTVALUE9DONTUSEINPRODUCTION99999JYFRTI'
                b'WMKVVBAIEIYZDWLUVOYTZBKPKLLUMPDF9PPFLO9KT',
            ),
)

response = await api.get_digests(...)

References:

create_multisig_address

MultisigIota.create_multisig_address(digests: Iterable[iota.crypto.types.Digest]) → dict

Generates a multisig address from a collection of digests.

Parameters

digests (Iterable[Digest]) –

Digests to use to create the multisig address.

Important

In order to spend IOTAs from a multisig address, the signature must be generated from the corresponding private keys in the exact same order.

Returns

dict with the following items:

{
    'address': MultisigAddress,
        The generated multisig address.
}

async AsyncMultisigIota.create_multisig_address(digests: Iterable[iota.crypto.types.Digest]) → dict

Generates a multisig address from a collection of digests.

Parameters

digests (Iterable[Digest]) –

Digests to use to create the multisig address.

Important

In order to spend IOTAs from a multisig address, the signature must be generated from the corresponding private keys in the exact same order.

Returns

dict with the following items:

{
    'address': MultisigAddress,
        The generated multisig address.
}

get_digests

MultisigIota.get_digests(index: int = 0, count: int = 1, security_level: int = 2) → dict

Generates one or more key digests from the seed.

Digests are safe to share; use them to generate multisig addresses.

Parameters
  • index (int) – The starting key index.

  • count (int) – Number of digests to generate.

  • security_level (int) –

    Number of iterations to use when generating new addresses.

    Larger values take longer, but the resulting signatures are more secure.

    This value must be between 1 and 3, inclusive.

Returns

dict with the following items:

{
    'digests': List[Digest],
        Always contains a list, even if only one digest
        was generated.
}

async AsyncMultisigIota.get_digests(index: int = 0, count: int = 1, security_level: int = 2) → dict

Generates one or more key digests from the seed.

Digests are safe to share; use them to generate multisig addresses.

Parameters
  • index (int) – The starting key index.

  • count (int) – Number of digests to generate.

  • security_level (int) –

    Number of iterations to use when generating new addresses.

    Larger values take longer, but the resulting signatures are more secure.

    This value must be between 1 and 3, inclusive.

Returns

dict with the following items:

{
    'digests': List[Digest],
        Always contains a list, even if only one digest
        was generated.
}

get_private_keys

MultisigIota.get_private_keys(index: int = 0, count: int = 1, security_level: int = 2) → dict

Generates one or more private keys from the seed.

As the name implies, private keys should not be shared. However, in a few cases it may be necessary (e.g., for M-of-N transactions).

Parameters
  • index (int) – The starting key index.

  • count (int) – Number of keys to generate.

  • security_level (int) –

    Number of iterations to use when generating new keys.

    Larger values take longer, but the resulting signatures are more secure.

    This value must be between 1 and 3, inclusive.

Returns

dict with the following items:

{
    'keys': List[PrivateKey],
        Always contains a list, even if only one key was
        generated.
}

References:

async AsyncMultisigIota.get_private_keys(index: int = 0, count: int = 1, security_level: int = 2) → dict

Generates one or more private keys from the seed.

As the name implies, private keys should not be shared. However, in a few cases it may be necessary (e.g., for M-of-N transactions).

Parameters
  • index (int) – The starting key index.

  • count (int) – Number of keys to generate.

  • security_level (int) –

    Number of iterations to use when generating new keys.

    Larger values take longer, but the resulting signatures are more secure.

    This value must be between 1 and 3, inclusive.

Returns

dict with the following items:

{
    'keys': List[PrivateKey],
        Always contains a list, even if only one key was
        generated.
}

References:

prepare_multisig_transfer

MultisigIota.prepare_multisig_transfer(transfers: Iterable[iota.transaction.creation.ProposedTransaction], multisig_input: iota.multisig.types.MultisigAddress, change_address: Optional[iota.types.Address] = None) → dict

Prepares a bundle that authorizes the spending of IOTAs from a multisig address.

Note

This method is used exclusively to spend IOTAs from a multisig address.

If you want to spend IOTAs from non-multisig addresses, or if you want to create 0-value transfers (i.e., that don’t require inputs), use prepare_transfer() instead.

Parameters
  • transfers (terable[ProposedTransaction]) –

    Transaction objects to prepare.

    Important

    Must include at least one transaction that spends IOTAs (i.e., has a nonzero value). If you want to prepare a bundle that does not spend any IOTAs, use prepare_transfer() instead.

  • multisig_input (MultisigAddress) –

    The multisig address to use as the input for the transfers.

    Note

    This method only supports creating a bundle with a single multisig input.

    If you would like to spend from multiple multisig addresses in the same bundle, create the ProposedMultisigBundle object manually.

  • change_address (Optional[Address]) –

    If inputs are provided, any unspent amount will be sent to this address.

    If the bundle has no unspent inputs, ``change_address` is ignored.

    Important

    Unlike prepare_transfer(), this method will NOT generate a change address automatically.

    If there are unspent inputs and change_address is empty, an exception will be raised.

    This is because multisig transactions typically involve multiple individuals, and it would be unfair to the participants if we generated a change address automatically using the seed of whoever happened to run the prepare_multisig_transfer method!

    Danger

    Note that this protective measure is not a substitute for due diligence!

    Always verify the details of every transaction in a bundle (including the change transaction) before signing the input(s)!

Returns

dict containing the following values:

{
    'trytes': List[TransactionTrytes],
        Finalized bundle, as trytes.
        The input transactions are not signed.
}

In order to authorize the spending of IOTAs from the multisig input, you must generate the correct private keys and invoke the iota.crypto.types.PrivateKey.sign_input_at() method for each key, in the correct order.

Once the correct signatures are applied, you can then perform proof of work (attachToTangle) and broadcast the bundle using send_trytes().

async AsyncMultisigIota.prepare_multisig_transfer(transfers: Iterable[iota.transaction.creation.ProposedTransaction], multisig_input: iota.multisig.types.MultisigAddress, change_address: Optional[iota.types.Address] = None) → dict

Prepares a bundle that authorizes the spending of IOTAs from a multisig address.

Note

This method is used exclusively to spend IOTAs from a multisig address.

If you want to spend IOTAs from non-multisig addresses, or if you want to create 0-value transfers (i.e., that don’t require inputs), use prepare_transfer() instead.

Parameters
  • transfers (terable[ProposedTransaction]) –

    Transaction objects to prepare.

    Important

    Must include at least one transaction that spends IOTAs (i.e., has a nonzero value). If you want to prepare a bundle that does not spend any IOTAs, use prepare_transfer() instead.

  • multisig_input (MultisigAddress) –

    The multisig address to use as the input for the transfers.

    Note

    This method only supports creating a bundle with a single multisig input.

    If you would like to spend from multiple multisig addresses in the same bundle, create the ProposedMultisigBundle object manually.

  • change_address (Optional[Address]) –

    If inputs are provided, any unspent amount will be sent to this address.

    If the bundle has no unspent inputs, ``change_address` is ignored.

    Important

    Unlike prepare_transfer(), this method will NOT generate a change address automatically.

    If there are unspent inputs and change_address is empty, an exception will be raised.

    This is because multisig transactions typically involve multiple individuals, and it would be unfair to the participants if we generated a change address automatically using the seed of whoever happened to run the prepare_multisig_transfer method!

    Danger

    Note that this protective measure is not a substitute for due diligence!

    Always verify the details of every transaction in a bundle (including the change transaction) before signing the input(s)!

Returns

dict wontaining the following values:

{
    'trytes': List[TransactionTrytes],
        Finalized bundle, as trytes.
        The input transactions are not signed.
}

In order to authorize the spending of IOTAs from the multisig input, you must generate the correct private keys and invoke the iota.crypto.types.PrivateKey.sign_input_at() method for each key, in the correct order.

Once the correct signatures are applied, you can then perform proof of work (attachToTangle) and broadcast the bundle using send_trytes().

PyOTA Multisignature Types

There are some specific types defined in PyOTA to help you work with creating multisignature addresses and bundles.

Multisignature Address

class iota.multisig.types.MultisigAddress(trytes: Union[AnyStr, bytearray, TryteString], digests: Iterable[iota.crypto.types.Digest], balance: Optional[int] = None)

An address that was generated using digests from multiple private keys.

In order to spend inputs from a multisig address, the same private keys must be used, in the same order that the corresponding digests were used to generate the address.

Note

Usually, you don’t have to create a MultisigAddress manually. Use create_multisig_address() to derive an address from a list of digests.

MultisigAddress is a subclass of iota.Address, so you can use all the regular iota.Address methods on a MultisigAddress object.

Parameters
  • trytes (TrytesCompatible) – Address trytes (81 trytes long).

  • digests (Iterable[Digest]) – List of digests that were used to create the address. Order is important!

  • balance (Optional[int]) – Available balance of the address.

Returns

MultisigAddress object.

as_json_compatible() → dict

Get a JSON represenation of the MultisigAddress object.

Returns

dict with the following structure:

{
    'trytes': str,
        String representation of the address trytes.
    'balance': int,
        Balance of the address.
    'digests': Iterable[Digest]
        Digests that were used to create the address.
}

Multisignature ProposedBundle

class iota.multisig.transaction.ProposedMultisigBundle(transactions: Optional[Iterable[iota.transaction.creation.ProposedTransaction]] = None, inputs: Optional[Iterable[iota.types.Address]] = None, change_address: Optional[iota.types.Address] = None)

A collection of proposed transactions, with multisig inputs.

Note: at this time, only a single multisig input is supported per bundle.

Note

Usually you don’t have to construct ProposedMultisigBundle bundle manually, prepare_multisig_transfer() does it for you.

Parameters
  • transactions (Optional[Iterable[ProposedTransaction]]) – Proposed transactions that should be put into the proposed bundle.

  • inputs (Optional[Iterable[Address]]) – Addresses that hold iotas to fund outgoing transactions in the bundle. Currently PyOTA supports only one mutlisig input address per bundle.

  • change_address (Optional[Address]) – Due to the signatures scheme of IOTA, you can only spend once from an address. Therefore the library will always deduct the full available amount from an input address. The unused tokens will be sent to change_address if provided.

Returns

ProposedMultisigBundle object.

add_inputs(inputs: Iterable[iota.multisig.types.MultisigAddress]) → None

Adds inputs to spend in the bundle.

Note that each input may require multiple transactions, in order to hold the entire signature.

Parameters

inputs (Iterable[MultisigAddress]) –

MultisigAddresses to use as the inputs for this bundle.

Note: at this time, only a single multisig input is supported.

Generating multisignature address

In order to use multisignature functionality, a special multisignature address must be created. It is done by adding each key digest in agreed order into digests list. At the end, last participant is converting digests list (Kerl state trits) into multisignature address.

Note

Each multisignature addresses participant has to create its own digest locally. Then, when it is created it can be safely shared with other participants, in order to build list of digests which then will be converted into multisignature address.

Created digests should be shared with each multisignature participant, so each one of them could regenerate address and ensure it is OK.

Here is the example where digest is created:

# Create digest 3 of 3.
api_3 =\
  MultisigIota(
    adapter = 'http://localhost:14265',

    seed =
      Seed(
        b'TESTVALUE9DONTUSEINPRODUCTION99999JYFRTI'
        b'WMKVVBAIEIYZDWLUVOYTZBKPKLLUMPDF9PPFLO9KT',
      ),
  )

gd_result = api_3.get_digests(index=8, count=1, security_level=2)

digest_3 = gd_result['digests'][0] # type: Digest

And here is example where digests are converted into multisignature address:

cma_result =\
  api_1.create_multisig_address(digests=[digest_1,
                                         digest_2,
                                         digest_3])

# For consistency, every API command returns a dict, even if it only
# has a single value.
multisig_address = cma_result['address'] # type: MultisigAddress

Note

As you can see in above example, multisignature addresses is created from list of digests, and in this case order is important. The same order need to be used in signing transfer.

Prepare transfer

Note

Since spending tokens from the same address more than once is insecure, remainder should be transferred to other address. So, this address should be created before as next to be used multisignature address.

First signer for multisignature wallet is defining address where tokens should be transferred and next wallet address for reminder:

pmt_result =\
  api_1.prepare_multisig_transfer(
    # These are the transactions that will spend the IOTAs.
    # You can divide up the IOTAs to send to multiple addresses if you
    # want, but to keep this example focused, we will only include a
    # single spend transaction.
    transfers = [
      ProposedTransaction(
        address =
          Address(
            b'TESTVALUE9DONTUSEINPRODUCTION99999NDGYBC'
            b'QZJFGGWZ9GBQFKDOLWMVILARZRHJMSYFZETZTHTZR',
          ),

        value = 42,

        # If you'd like, you may include an optional tag and/or
        # message.
        tag = Tag(b'KITTEHS'),
        message = TryteString.from_unicode('thanx fur cheezburgers'),
      ),
    ],

    # Specify our multisig address as the input for the spend
    # transaction(s).
    # Note that PyOTA currently only allows one multisig input per
    # bundle (although the protocol does not impose a limit).
    multisig_input = multisig_address,

    # If there will be change from this transaction, you MUST specify
    # the change address!  Unlike regular transfers, multisig transfers
    # will NOT automatically generate a change address; that wouldn't
    # be fair to the other participants!
    change_address = None,
  )

prepared_trytes = pmt_result['trytes'] # type: List[TransactionTrytes]

Sign the inputs

When trytes are prepared, round of signing must be performed. Order of signing must be the same as in generate multisignature addresses procedure (as described above).

Note

In example below, all signing is done on one local machine. In real case, each participant sign bundle locally and then passes it to next participant in previously defined order

index, count and security_lavel parameters for each private key should be the same as used in get_digests function in previous steps.

bundle = Bundle.from_tryte_strings(prepared_trytes)

gpk_result = api_1.get_private_keys(index=0, count=1, security_level=3)
private_key_1 = gpk_result['keys'][0] # type: PrivateKey
private_key_1.sign_input_transactions(bundle, 1)

gpk_result = api_2.get_private_keys(index=42, count=1, security_level=3)
private_key_2 = gpk_result['keys'][0] # type: PrivateKey
private_key_2.sign_input_transactions(bundle, 4)

gpk_result = api_3.get_private_keys(index=8, count=1, security_level=2)
private_key_3 = gpk_result['keys'][0] # type: PrivateKey
private_key_3.sign_input_transactions(bundle, 7)

signed_trytes = bundle.as_tryte_strings()

Note

After creation, bundle can be optionally validated:

validator = BundleValidator(bundle)
if not validator.is_valid():
  raise ValueError(
    'Bundle failed validation:\n{errors}'.format(
      errors = '\n'.join(('  - ' + e) for e in validator.errors),
    ),
  )

Broadcast the bundle

When bundle is created it can be broadcasted in standard way:

api_1.send_trytes(trytes=signed_trytes, depth=3)

Remarks

Full code example.

Note

How M-of-N works

One of the key differences between IOTA multi-signatures is that M-of-N (e.g. 3 of 5) works differently. What this means is that in order to successfully spend inputs, all of the co-signers have to sign the transaction. As such, in order to enable M-of-N we have to make use of a simple trick: sharing of private keys.

This concept is best explained with a concrete example:

Lets say that we have a multi-signature between 3 parties: Alice, Bob and Carol. Each has their own private key, and they generated a new multi-signature address in the aforementioned order. Currently, this is a 3 of 3 multisig. This means that all 3 participants (Alice, Bob and Carol) need to sign the inputs with their private keys in order to successfully spend them.

In order to enable a 2 of 3 multisig, the cosigners need to share their private keys with the other parties in such a way that no single party can sign inputs alone, but that still enables an M-of-N multsig. In our example, the sharing of the private keys would look as follows:

Alice -> Bob

Bob -> Carol

Carol -> Alice

Now, each participant holds two private keys that he/she can use to collude with another party to successfully sign the inputs and make a transaction. But no single party holds enough keys (3 of 3) to be able to independently make the transaction.

Important

There are some general rules (repeated once again for convenience) which should be followed while working with multisignature addresses (and in general with IOTA):

Signing order is important

When creating a multi-signature address and when signing a transaction for that address, it is important to follow the exact order that was used during the initial creation. If we have a multi-signature address that was signed in the following order: Alice -> Bob -> Carol. You will not be able to spend these inputs if you provide the signatures in a different order (e.g. Bob -> Alice -> Carol). As such, keep the signing order in mind.

Never re-use keys

Probably the most important rule to keep in mind: absolutely never re-use private keys. IOTA uses one-time Winternitz signatures, which means that if you re-use private keys you significantly decrease the security of your private keys, up to the point where signing of another transaction can be done on a conventional computer within few days. Therefore, when generating a new multi-signature with your co-signers, always increase the private key index counter and only use a single private key once. Don’t use it for any other multi-signatures and don’t use it for any personal transactions.

Never share your private keys

Under no circumstances - other than wanting to reduce the requirements for a multi-signature (see section How M-of-N works) - should you share your private keys. Sharing your private keys with others means that they can sign your part of the multi-signature successfully.