From e3153352a0dc6aa2567a4ac883f5900b54075f15 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 18 Feb 2020 14:55:22 +0100 Subject: [PATCH 1/2] docs: update API docs with async --- docs/api.rst | 56 ++++++++++++++++++++++++++++++++++--------- docs/core_api.rst | 39 +++++++++++++++++++++++++++++- docs/extended_api.rst | 37 ++++++++++++++++++++++++++++ iota/api_async.py | 2 +- 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e74743e6..b339a8e2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -15,8 +15,12 @@ The available methods can be grouped into two categories:** | | | +------------------------------------+------------------------------------+ +**PyOTA supports both synchronous and asynchronous communication with the network, +therefore the Core and Extended API classes are available in synchronous and +asynchronous versions.** + To use the API in your Python application or script, declare an -API instance of any of the two above. +API instance of any of the API classes. **Since the Extended API incorporates the Core API, usually you end up only using the Extended API,** but if for some reason you need only the core functionality, the library is there to help you. @@ -25,12 +29,13 @@ functionality, the library is there to help you. :linenos: :emphasize-lines: 3,4 + # Synchronous API classes from iota import Iota, StrictIota - # This is how you declare an Extended API, use the methods of this object. + # This is how you declare a sync Extended API, use the methods of this object. api = Iota('adapter-specification') - # This is how you declare a Core API, use the methods of this object. + # This is how you declare a sync Core API, use the methods of this object. api = StrictIota('adapter-specification') .. py:module:: iota @@ -41,19 +46,48 @@ implementation point of view, :py:class:`Iota` is a subclass of :py:class:`StrictIota`, therefore it inherits every method and attribute the latter has. -Take a look on the class definitions and notice that :py:class:`Iota` -has a :py:class:`Seed` attribute. This is becasue the Extended API is able -to generate private keys, addresses and signatures from your seed. -**Your seed never leaves the library and your machine!** +To use the functionally same, but asynchronous API classes, you can do the +following: + +.. code-block:: + :linenos: + :emphasize-lines: 3,4 + + # Asynchronous API classes + from iota import AsyncIota, AsyncStrictIota -Core API Class --------------- + # This is how you declare an async Extended API, use the methods of this object. + api = AsyncIota('adapter-specification') + # This is how you declare an async Core API, use the methods of this object. + api = AsyncStrictIota('adapter-specification') + + +Take a look on the class definitions and notice that :py:class:`Iota` and +:py:class:`AsyncIota` have a :py:class:`Seed` attribute. This is because the +Extended API is able to generate private keys, addresses and signatures from +your seed. **Your seed never leaves the library and your machine!** + +Core API Classes +---------------- +Synchronous +^^^^^^^^^^^ .. autoclass:: StrictIota :members: set_local_pow -Extended API Class ------------------- +Asynchronous +^^^^^^^^^^^^ +.. autoclass:: AsyncStrictIota + :members: set_local_pow +Extended API Classes +-------------------- +Synchronous +^^^^^^^^^^^ .. autoclass:: Iota :members: set_local_pow + +Asynchronous +^^^^^^^^^^^^ +.. autoclass:: AsyncIota + :members: set_local_pow diff --git a/docs/core_api.rst b/docs/core_api.rst index 37d4d5d0..529b71fa 100644 --- a/docs/core_api.rst +++ b/docs/core_api.rst @@ -12,76 +12,113 @@ For the full documentation of all the Core API calls, please refer to the `official documentation `__. +.. note:: + Below you will find the documentation for both the synchronous and + asynchronous versions of the Core API methods. + + It should be made clear, that they do exactly the same IOTA related + operations, accept the same arguments and return the same structures. + Asynchronous API calls are non-blocking, so your application + can do other stuff while waiting for the result from the network. + + While synchronous API calls are regular Python methods, their respective + asynchronous versions are `Python coroutines`_. You can ``await`` their + results, schedule them for execution inside and event loop and much more. + PyOTA uses the built-in `asyncio`_ Python module for asynchronous operation. + For an overview of what you can do with it, head over to `this article`_. + .. py:currentmodule:: iota ``add_neighbors`` ----------------- .. automethod:: Iota.add_neighbors +.. automethod:: AsyncIota.add_neighbors ``attach_to_tangle`` -------------------- .. automethod:: Iota.attach_to_tangle +.. automethod:: AsyncIota.attach_to_tangle ``broadcast_transactions`` -------------------------- .. automethod:: Iota.broadcast_transactions +.. automethod:: AsyncIota.broadcast_transactions ``check_consistency`` --------------------- .. automethod:: Iota.check_consistency +.. automethod:: AsyncIota.check_consistency ``find_transactions`` --------------------- .. automethod:: Iota.find_transactions +.. automethod:: AsyncIota.find_transactions ``get_balances`` ---------------- .. automethod:: Iota.get_balances +.. automethod:: AsyncIota.get_balances ``get_inclusion_states`` ------------------------ .. automethod:: Iota.get_inclusion_states +.. automethod:: AsyncIota.get_inclusion_states ``get_missing_transactions`` ---------------------------- .. automethod:: Iota.get_missing_transactions +.. automethod:: AsyncIota.get_missing_transactions ``get_neighbors`` ----------------- .. automethod:: Iota.get_neighbors +.. automethod:: AsyncIota.get_neighbors ``get_node_api_configuration`` ------------------------------ .. automethod:: Iota.get_node_api_configuration +.. automethod:: AsyncIota.get_node_api_configuration ``get_node_info`` ----------------- .. automethod:: Iota.get_node_info +.. automethod:: AsyncIota.get_node_info ``get_tips`` ------------ .. automethod:: Iota.get_tips +.. automethod:: AsyncIota.get_tips ``get_transactions_to_approve`` ------------------------------- .. automethod:: Iota.get_transactions_to_approve +.. automethod:: AsyncIota.get_transactions_to_approve ``get_trytes`` -------------- .. automethod:: Iota.get_trytes +.. automethod:: AsyncIota.get_trytes ``interrupt_attaching_to_tangle`` --------------------------------- .. automethod:: Iota.interrupt_attaching_to_tangle +.. automethod:: AsyncIota.interrupt_attaching_to_tangle ``remove_neighbors`` -------------------- .. automethod:: Iota.remove_neighbors +.. automethod:: AsyncIota.remove_neighbors ``store_transactions`` ---------------------- .. automethod:: Iota.store_transactions +.. automethod:: AsyncIota.store_transactions ``were_addresses_spent_from`` ----------------------------- -.. automethod:: Iota.were_addresses_spent_from \ No newline at end of file +.. automethod:: Iota.were_addresses_spent_from +.. automethod:: AsyncIota.were_addresses_spent_from + +.. _Python coroutines: https://docs.python.org/3/library/asyncio-task.html +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _this article: https://realpython.com/async-io-python/ \ No newline at end of file diff --git a/docs/extended_api.rst b/docs/extended_api.rst index 59b23b85..abb437af 100644 --- a/docs/extended_api.rst +++ b/docs/extended_api.rst @@ -4,77 +4,114 @@ Extended API Methods The Extended API includes a number of "high level" commands to perform tasks such as sending and receiving transfers. +.. note:: + Below you will find the documentation for both the synchronous and + asynchronous versions of the Extebded API methods. + + It should be made clear, that they do exactly the same IOTA related + operations, accept the same arguments and return the same structures. + Asynchronous API calls are non-blocking, so your application + can do other stuff while waiting for the result from the network. + + While synchronous API calls are regular Python methods, their respective + asynchronous versions are `Python coroutines`_. You can ``await`` their + results, schedule them for execution inside and event loop and much more. + PyOTA uses the built-in `asyncio`_ Python module for asynchronous operation. + For an overview of what you can do with it, head over to `this article`_. + + .. py:currentmodule:: iota ``broadcast_and_store`` ----------------------- .. automethod:: Iota.broadcast_and_store +.. automethod:: AsyncIota.broadcast_and_store ``broadcast_bundle`` -------------------- .. automethod:: Iota.broadcast_bundle +.. automethod:: AsyncIota.broadcast_bundle ``find_transaction_objects`` ---------------------------- .. automethod:: Iota.find_transaction_objects +.. automethod:: AsyncIota.find_transaction_objects ``get_account_data`` -------------------- .. automethod:: Iota.get_account_data +.. automethod:: AsyncIota.get_account_data ``get_bundles`` --------------- .. automethod:: Iota.get_bundles +.. automethod:: AsyncIota.get_bundles ``get_inputs`` -------------- .. automethod:: Iota.get_inputs +.. automethod:: AsyncIota.get_inputs ``get_latest_inclusion`` ------------------------ .. automethod:: Iota.get_latest_inclusion +.. automethod:: AsyncIota.get_latest_inclusion ``get_new_addresses`` --------------------- .. automethod:: Iota.get_new_addresses +.. automethod:: AsyncIota.get_new_addresses ``get_transaction_objects`` --------------------------- .. automethod:: Iota.get_transaction_objects +.. automethod:: AsyncIota.get_transaction_objects ``get_transfers`` ----------------- .. automethod:: Iota.get_transfers +.. automethod:: AsyncIota.get_transfers ``is_promotable`` ----------------- .. automethod:: Iota.is_promotable +.. automethod:: AsyncIota.is_promotable ``is_reattachable`` ------------------- .. automethod:: Iota.is_reattachable +.. automethod:: AsyncIota.is_reattachable ``prepare_transfer`` -------------------- .. automethod:: Iota.prepare_transfer +.. automethod:: AsyncIota.prepare_transfer ``promote_transaction`` ----------------------- .. automethod:: Iota.promote_transaction +.. automethod:: AsyncIota.promote_transaction ``replay_bundle`` ----------------- .. automethod:: Iota.replay_bundle +.. automethod:: AsyncIota.replay_bundle ``send_transfer`` ----------------- .. automethod:: Iota.send_transfer +.. automethod:: AsyncIota.send_transfer ``send_trytes`` --------------- .. automethod:: Iota.send_trytes +.. automethod:: AsyncIota.send_trytes ``traverse_bundle`` ------------------- .. automethod:: Iota.traverse_bundle +.. automethod:: AsyncIota.traverse_bundle +.. _Python coroutines: https://docs.python.org/3/library/asyncio-task.html +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _this article: https://realpython.com/async-io-python/ \ No newline at end of file diff --git a/iota/api_async.py b/iota/api_async.py index 705e2e70..60042420 100644 --- a/iota/api_async.py +++ b/iota/api_async.py @@ -19,7 +19,7 @@ class AsyncStrictIota: Asynchronous API to send HTTP requests for communicating with an IOTA node. This implementation only exposes the "core" API methods. For a more - feature-complete implementation, use :py:class:`Iota` instead. + feature-complete implementation, use :py:class:`AsyncIota` instead. References: From 11474353cf1733f38edcac95f90151fa032582f0 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 19 Feb 2020 13:09:38 +0100 Subject: [PATCH 2/2] docs: Update multisigs, add info on async --- docs/multisig.rst | 156 ++++++++++++++++++++++++++++++----- iota/multisig/api.py | 98 +++++++++++++++------- iota/multisig/transaction.py | 23 +++++- iota/multisig/types.py | 39 +++++++++ 4 files changed, 264 insertions(+), 52 deletions(-) diff --git a/docs/multisig.rst b/docs/multisig.rst index 7b350c2b..e533d0c8 100644 --- a/docs/multisig.rst +++ b/docs/multisig.rst @@ -1,24 +1,98 @@ 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. +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 +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. + +.. py:currentmodule:: iota.multisig + +Synchronous Multisignature API Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: MultisigIota + + +Asynchronous Multisignature API Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: AsyncMultisigIota + +``create_multisig_address`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.create_multisig_address +.. automethod:: AsyncMultisigIota.create_multisig_address + +``get_digests`` +~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.get_digests +.. automethod:: AsyncMultisigIota.get_digests + +``get_private_keys`` +~~~~~~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.get_private_keys +.. automethod:: AsyncMultisigIota.get_private_keys + +``prepare_multisig_transfer`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.prepare_multisig_transfer +.. automethod:: AsyncMultisigIota.prepare_multisig_transfer + +PyOTA Multisignature Types +-------------------------- + +There are some specific types defined in PyOTA to help you work with creating +multisignature addresses and bundles. + +Multisignature Address +~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: iota.multisig.types.MultisigAddress + :members: as_json_compatible + +Multisignature ProposedBundle +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: iota.multisig.transaction.ProposedMultisigBundle + :members: add_inputs + 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 (Curl state trits) into 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. + 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. + 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: @@ -55,9 +129,9 @@ And here is example where digests are converted into multisignature address: .. 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**. - - + 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 @@ -65,9 +139,12 @@ 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. + 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: +First signer for multisignature wallet is defining address where tokens should +be transferred and next wallet address for reminder: .. code-block:: python @@ -113,13 +190,18 @@ First signer for multisignature wallet is defining address where tokens should b 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). +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 + 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. + **index**, **count** and **security_lavel** parameters for each private key + should be the same as used in **get_digests** function in previous steps. .. code-block:: python @@ -173,13 +255,25 @@ Full code `example`_. 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. + 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. + 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: + 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 @@ -187,27 +281,47 @@ Full code `example`_. 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. + 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): +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. +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. +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. +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. .. _example: https://github.com/iotaledger/iota.py/blob/develop/examples/multisig.py .. _wiki: https://github.com/iotaledger/wiki/blob/master/multisigs.md +.. _here: https://github.com/iotaledger/iota.py/blob/master/examples/multisig.py diff --git a/iota/multisig/api.py b/iota/multisig/api.py index d345138d..54ec62ce 100644 --- a/iota/multisig/api.py +++ b/iota/multisig/api.py @@ -28,6 +28,24 @@ class AsyncMultisigIota(AsyncIota): 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: - https://github.com/iotaledger/wiki/blob/master/multisigs.md @@ -38,7 +56,7 @@ async def create_multisig_address(self, digests): """ Generates a multisig address from a collection of digests. - :param digests: + :param Iterable[Digest] digests: Digests to use to create the multisig address. .. important:: @@ -71,13 +89,13 @@ async def get_digests( Digests are safe to share; use them to generate multisig addresses. - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of digests to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new addresses. Larger values take longer, but the resulting signatures are @@ -115,13 +133,13 @@ async def get_private_keys( However, in a few cases it may be necessary (e.g., for M-of-N transactions). - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of keys to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new keys. Larger values take longer, but the resulting signatures are @@ -168,18 +186,18 @@ async def prepare_multisig_transfer( 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 - :py:meth:`iota.api.Iota.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param transfers: + :param terable[ProposedTransaction] transfers: 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 - :py:meth:`iota.api.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param multisig_input: + :param MultisigAddress multisig_input: The multisig address to use as the input for the transfers. .. note:: @@ -188,10 +206,10 @@ async def prepare_multisig_transfer( If you would like to spend from multiple multisig addresses in the same bundle, create the - :py:class:`iota.multisig.transaction.ProposedMultisigBundle` + :py:class:`~iota.multisig.transaction.ProposedMultisigBundle` object manually. - :param change_address: + :param Optional[Address] change_address: If inputs are provided, any unspent amount will be sent to this address. @@ -199,8 +217,9 @@ async def prepare_multisig_transfer( ignored. .. important:: - Unlike :py:meth:`iota.api.Iota.prepare_transfer`, this + Unlike :py:meth:`~iota.Iota.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. @@ -234,7 +253,7 @@ async def prepare_multisig_transfer( Once the correct signatures are applied, you can then perform proof of work (``attachToTangle``) and broadcast the bundle - using :py:meth:`iota.api.Iota.send_trytes`. + using :py:meth:`~iota.Iota.send_trytes`. """ return await commands.PrepareMultisigTransferCommand(self.adapter)( changeAddress=change_address, @@ -253,6 +272,24 @@ class MultisigIota(Iota, AsyncMultisigIota): 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: - https://github.com/iotaledger/wiki/blob/master/multisigs.md @@ -263,7 +300,7 @@ def create_multisig_address(self, digests): """ Generates a multisig address from a collection of digests. - :param digests: + :param Iterable[Digest] digests: Digests to use to create the multisig address. .. important:: @@ -296,13 +333,13 @@ def get_digests( Digests are safe to share; use them to generate multisig addresses. - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of digests to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new addresses. Larger values take longer, but the resulting signatures are @@ -341,13 +378,13 @@ def get_private_keys( However, in a few cases it may be necessary (e.g., for M-of-N transactions). - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of keys to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new keys. Larger values take longer, but the resulting signatures are @@ -395,18 +432,18 @@ def prepare_multisig_transfer( 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 - :py:meth:`iota.api.Iota.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param transfers: + :param terable[ProposedTransaction] transfers: 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 - :py:meth:`iota.api.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param multisig_input: + :param MultisigAddress multisig_input: The multisig address to use as the input for the transfers. .. note:: @@ -415,10 +452,10 @@ def prepare_multisig_transfer( If you would like to spend from multiple multisig addresses in the same bundle, create the - :py:class:`iota.multisig.transaction.ProposedMultisigBundle` + :py:class:`~iota.multisig.transaction.ProposedMultisigBundle` object manually. - :param change_address: + :param Optional[Address] change_address: If inputs are provided, any unspent amount will be sent to this address. @@ -426,8 +463,9 @@ def prepare_multisig_transfer( ignored. .. important:: - Unlike :py:meth:`iota.api.Iota.prepare_transfer`, this + Unlike :py:meth:`~iota.Iota.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. @@ -461,7 +499,7 @@ def prepare_multisig_transfer( Once the correct signatures are applied, you can then perform proof of work (``attachToTangle``) and broadcast the bundle - using :py:meth:`iota.api.Iota.send_trytes`. + using :py:meth:`~iota.Iota.send_trytes`. """ return asyncio.get_event_loop().run_until_complete( super(MultisigIota, self).prepare_multisig_transfer( diff --git a/iota/multisig/transaction.py b/iota/multisig/transaction.py index 42e3d9d6..2974a0a0 100644 --- a/iota/multisig/transaction.py +++ b/iota/multisig/transaction.py @@ -12,12 +12,33 @@ 'ProposedMultisigBundle', ] + class ProposedMultisigBundle(ProposedBundle): """ 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 :py:class:`ProposedMultisigBundle` + bundle manually, :py:meth:`~iota.multisig.MultisigIota.prepare_multisig_transfer` + does it for you. + + :param Optional[Iterable[ProposedTransaction]] transactions: + Proposed transactions that should be put into the proposed bundle. + + :param Optional[Iterable[Address]] inputs: + Addresses that hold iotas to fund outgoing transactions in the bundle. + Currently PyOTA supports only one mutlisig input address per bundle. + + :param Optional[Address] change_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. + + :return: :py:class:`ProposedMultisigBundle` object. """ def add_inputs(self, inputs): # type: (Iterable[MultisigAddress]) -> None @@ -27,7 +48,7 @@ def add_inputs(self, inputs): Note that each input may require multiple transactions, in order to hold the entire signature. - :param inputs: + :param Iterable[MultisigAddress] inputs: MultisigAddresses to use as the inputs for this bundle. Note: at this time, only a single multisig input is supported. diff --git a/iota/multisig/types.py b/iota/multisig/types.py index b9e9fa15..4b68a811 100644 --- a/iota/multisig/types.py +++ b/iota/multisig/types.py @@ -21,6 +21,29 @@ class MultisigAddress(Address): 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 + :py:meth:`~iota.multisig.MultisigIota.create_multisig_address` to + derive an address from a list of digests. + + :py:class:`MultisigAddress` is a subclass of + :py:class:`iota.Address`, so you can use all the regular + :py:class:`iota.Address` methods on a :py:class:`MultisigAddress` + object. + + :param TrytesCompatible trytes: + Address trytes (81 trytes long). + + :param Iterable[Digest] digests: + List of digests that were used to create the address. + Order is important! + + :param Optional[int] balance: + Available balance of the address. + + :return: + :py:class:`MultisigAddress` object. """ def __init__(self, trytes, digests, balance=None): @@ -35,6 +58,22 @@ def __init__(self, trytes, digests, balance=None): ) def as_json_compatible(self): + """ + Get a JSON represenation of the :py:class:`MultisigAddress` object. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': Text, + String representation of the address trytes. + 'balance': int, + Balance of the address. + 'digests': Iterable[Digest] + Digests that were used to create the address. + } + + """ # type: () -> dict return { 'trytes': self._trytes.decode('ascii'),