diff --git a/docs/basic_concepts.rst b/docs/basic_concepts.rst index b2fca72..fed8885 100644 --- a/docs/basic_concepts.rst +++ b/docs/basic_concepts.rst @@ -140,8 +140,8 @@ field that is a `unique identifier for the bundle`_. :alt: Bundle structure with four transactions. Structure of a bundle with four transactions. Numbers in brackets denote - (``currentIndex``, ``lastIndex``) fields. Head of the bundle has index 0, - while tail has index 3. + (``currentIndex``, ``lastIndex``) fields. Head of the bundle has index 3, + while tail has index 0. Read more about `how bundles are structured`_. diff --git a/docs/types.rst b/docs/types.rst index a456f55..b1780b1 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -4,74 +4,75 @@ PyOTA Types PyOTA defines a few types that will make it easy for you to model objects like Transactions and Bundles in your own code. +Since everything in IOTA is represented as a sequence of ``trits`` and ``trytes``, +let us take a look on how you can work with them in PyOTA. + TryteString ----------- +.. py:currentmodule:: iota + +.. autoclass:: TryteString + +Example usage: .. code:: python from iota import TryteString + # Create a TryteString object from bytes. trytes_1 = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA') - trytes_2 = TryteString(b'LH9GYEMHCF9GWHZFEELHVFOEOHNEEEWHZFUD') + # Ensure the created object is 81 trytes long by padding it with zeros. + # The value zero is represented with character '9' in trytes. + trytes_1 = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA', pad=81) + + # Create a TryteString object from text type. + # Note that this will throw error if text contains unsupported characters. + trytes_2 = TryteString('LH9GYEMHCF9GWHZFEELHVFOEOHNEEEWHZFUD') + + # Comparison and concatenation: if trytes_1 != trytes_2: trytes_combined = trytes_1 + trytes_2 + # As dictionary keys: index = { trytes_1: 42, trytes_2: 86, } -A ``TryteString`` is an ASCII representation of a sequence of trytes. In -many respects, it is similar to a Python ``bytes`` object (which is an -ASCII representation of a sequence of bytes). - -In fact, the two objects behave very similarly; they support -concatenation, comparison, can be used as dict keys, etc. - -However, unlike ``bytes``, a ``TryteString`` can only contain uppercase -letters and the number 9 (as a regular expression: ``^[A-Z9]*$``). - As you go through the API documentation, you will see many references to -``TryteString`` and its subclasses: +:py:class:`TryteString` and its subclasses: -- ``Fragment``: A signature or message fragment inside a transaction. +- :py:class:`Fragment`: A signature or message fragment inside a transaction. Fragments are always 2187 trytes long. -- ``Hash``: An object identifier. Hashes are always 81 trytes long. +- :py:class:`Hash`: An object identifier. Hashes are always 81 trytes long. There are many different types of hashes: -- ``Address``: Identifies an address on the Tangle. -- ``BundleHash``: Identifies a bundle on the Tangle. -- ``TransactionHash``: Identifies a transaction on the Tangle. -- ``Seed``: A TryteString that is used for crypto functions such as +- :py:class:`Address`: Identifies an address on the Tangle. +- :py:class:`BundleHash`: Identifies a bundle on the Tangle. +- :py:class:`TransactionHash`: Identifies a transaction on the Tangle. +- :py:class:`Seed`: A TryteString that is used for crypto functions such as generating addresses, signing inputs, etc. Seeds can be any length, but 81 trytes offers the best security. -- ``Tag``: A tag used to classify a transaction. Tags are always 27 +- :py:class:`Tag`: A tag used to classify a transaction. Tags are always 27 trytes long. -- ``TransactionTrytes``: A TryteString representation of a transaction +- :py:class:`TransactionTrytes`: A TryteString representation of a transaction on the Tangle. ``TransactionTrytes`` are always 2673 trytes long. +Let's explore the capabilities of the :py:class:`TryteString` base class. + Encoding ~~~~~~~~ -.. code:: python +You may use classmethods to create a :py:class:`TryteString` from ``bytes``, +``unicode string`` or from a list of ``trits``. - from iota import TryteString +**from_bytes** +^^^^^^^^^^^^^^ +.. automethod:: TryteString.from_bytes - message_trytes = TryteString.from_unicode('Hello, IOTA!') - -To encode character data into trytes, use the -``TryteString.from_unicode`` method. - -You can also convert a tryte sequence into characters using -``TryteString.decode``. Note that not every tryte sequence can be -converted; garbage in, garbage out! - -.. code:: python - - from iota import TryteString - - trytes = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA') - message = trytes.decode() +**from_unicode** +^^^^^^^^^^^^^^^^ +.. automethod:: TryteString.from_unicode .. note:: @@ -83,205 +84,269 @@ converted; garbage in, garbage out! use ASCII characters when generating ``TryteString`` objects from character strings. +**from_trits** +^^^^^^^^^^^^^^ +.. automethod:: TryteString.from_trits + +**from_trytes** +^^^^^^^^^^^^^^^ +.. automethod:: TryteString.from_trytes + +Additionally, you can encode a :py:class:`TryteString` into a lower-level +primitive (usually bytes). This might be useful when the :py:class:`TryteString` +contains ASCII encoded characters but you need it as ``bytes``. See the example +below: + +**encode** +^^^^^^^^^^ +.. automethod:: TryteString.encode + + +Decoding +~~~~~~~~ + +You can also convert a tryte sequence into characters using +:py:meth:`TryteString.decode`. Note that not every tryte sequence can be +converted; garbage in, garbage out! + +**decode** +^^^^^^^^^^ +.. automethod:: TryteString.decode + +**as_json_compatible** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: TryteString.as_json_compatible + +**as_integers** +^^^^^^^^^^^^^^^ +.. automethod:: TryteString.as_integers + +**as_trytes** +^^^^^^^^^^^^^ +.. automethod:: TryteString.as_trytes + +**as_trits** +^^^^^^^^^^^^ +.. automethod:: TryteString.as_trits + +Seed +---- +.. autoclass:: Seed + +**random** +~~~~~~~~~~ +.. automethod:: Seed.random + +Address +------- +.. autoclass:: Address + :members: address, balance, key_index, security_level + +**as_json_compatible** +~~~~~~~~~~~~~~~~~~~~~~ +.. automethod:: Address.as_json_compatible + +**is_checksum_valid** +~~~~~~~~~~~~~~~~~~~~~ +.. automethod:: Address.is_checksum_valid + +**with_valid_checksum** +~~~~~~~~~~~~~~~~~~~~~~~ +.. automethod:: Address.with_valid_checksum + +**add_checksum** +~~~~~~~~~~~~~~~~ +.. automethod:: Address.add_checksum + +**remove_checksum** +~~~~~~~~~~~~~~~~~~~ +.. automethod:: Address.remove_checksum + +AddressChecksum +--------------- +.. autoclass:: AddressChecksum + :members: + +Hash +---- +.. autoclass:: Hash + :members: + +TransactionHash +--------------- +.. autoclass:: TransactionHash + :members: + +BundleHash +---------- +.. autoclass:: BundleHash + :members: + +TransactionTrytes +----------------- +.. autoclass:: TransactionTrytes + :members: + +Fragment +-------- +.. autoclass:: Fragment + :members: + +Nonce +----- +.. autoclass:: Nonce + :members: + +Tag +--- +.. autoclass:: Tag + :members: + Transaction Types ----------------- PyOTA defines two different types used to represent transactions: + - :py:class:`Transaction` for transactions that have already been + attached to the Tangle. Generally, you will never need to create + :py:class:`Transaction` objects; the API will build them for you, + as the result of various API methods. + - :py:class:`ProposedTransaction` for transactions that have been created + locally and have not been broadcast yet. + Transaction ~~~~~~~~~~~ - -.. code:: python - - from iota import Address, ProposedTransaction, Tag, Transaction - - txn_1 =\ - Transaction.from_tryte_string( - b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' - b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ' - b'WIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTS' - b'VPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRB' - b'XIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRY' - b'WHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9' - b'JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCD' - b'X9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRG' - b'YI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LV' - b'PJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEB' - b'CS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASP' - b'ZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYR' - b'TKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJD' - b'XMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHB' - b'DZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROH' - b'BETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQE' - b'EWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWE' - b'IDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLH' - b'LUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPI' - b'XNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTC' - b'DMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRA' - b'BQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGO' - b'XQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKU' - b'ROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCP' - b'JUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENI' - b'XRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQ' - b'UBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999' - b'999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSK' - b'UCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999' - ) - -``Transaction`` is a transaction that has been loaded from the Tangle. - -Generally, you will never need to create ``Transaction`` objects; the -API will build them for you, as the result of various API methods. - -Each ``Transaction`` has the following attributes: - -- ``address: Address``: The address associated with this transaction. - Depending on the transaction's ``value``, this address may be a - sender or a recipient. -- ``attachment_timestamp: int``: Estimated epoch time of the attachment to the tangle. -- ``attachment_time_lower_bound: int``: The lowest possible epoch time of the attachment to the tangle. -- ``attachment_time_upper_bound: int``: The highest possible epoch time of the attachment to the tangle. -- ``branch_transaction_hash: TransactionHash``: An unrelated - transaction that this transaction "approves". Refer to the Basic - Concepts section for more information. -- ``bundle_hash: BundleHash``: The bundle hash, used to identify - transactions that are part of the same bundle. This value is - generated by taking a hash of the metadata from all transactions in - the bundle. -- ``current_index: int``: The transaction's position in the bundle. -- If the ``current_index`` value is 0, then this is the "tail - transaction". -- If it is equal to ``last_index``, then this is the "head - transaction". -- ``hash: TransactionHash``: The transaction hash, used to uniquely - identify the transaction on the Tangle. This value is generated by - taking a hash of the raw transaction trits. -- ``is_confirmed: Optional[bool]``: Whether this transaction has been - "confirmed". Refer to the Basic Concepts section for more - information. -- ``last_index: int``: The index of the final transaction in the - bundle. This value is attached to every transaction to make it easier - to traverse and verify bundles. -- ``legacy_tag: Tag``: A short message attached to the transaction. Deprecated, use ``tag`` instead. -- ``nonce: Hash``: This is the product of the PoW process. -- ``signature_message_fragment: Fragment``: Additional data attached to - the transaction: -- If ``value < 0``, this value contains a fragment of the cryptographic - signature authorizing the spending of the IOTAs. -- If ``value > 0``, this value is an (optional) string message attached - to the transaction. -- If ``value = 0``, this value could be either a signature or message - fragment, depending on the previous transaction. -- ``tag: Tag``: Used to classify the transaction. Many transactions - have empty tags (``Tag(b'999999999999999999999999999')``). -- ``timestamp: int``: Unix timestamp when the transaction was created. - Note that devices can specify any timestamp when creating - transactions, so this value is not safe to use for security measures - (such as resolving double-spends). -- ``trunk_transaction_hash: TransactionHash``: The transaction hash of - the next transaction in the bundle. If this transaction is the head - transaction, its ``trunk_transaction_hash`` will be pseudo-randomly - selected, similarly to ``branch_transaction_hash``. -- ``value: int``: The number of IOTAs being transferred in this - transaction: -- If this value is negative, then the ``address`` is spending IOTAs. -- If it is positive, then the ``address`` is receiving IOTAs. -- If it is zero, then this transaction is being used to carry metadata - (such as a signature fragment or a message) instead of transferring - IOTAs. +Each :py:class:`Transaction` object has several instance attributes that you +may manipulate and properties you can use to extract their values as trytes. +See the class documentation below: + +.. autoclass:: Transaction + :members: hash, bundle_hash, address, value, nonce, timestamp, + current_index, last_index, trunk_transaction_hash, branch_transaction_hash, + tag, attachment_timestamp, attachment_timestamp_lower_bound, + attachment_timestamp_upper_bound, signature_message_fragment, is_confirmed, + is_tail, value_as_trytes, timestamp_as_trytes, current_index_as_trytes, + last_index_as_trytes, attachment_timestamp_as_trytes, + attachment_timestamp_lower_bound_as_trytes, + attachment_timestamp_upper_bound_as_trytes, legacy_tag + +**as_json_compatible** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Transaction.as_json_compatible + +**as_tryte_string** +^^^^^^^^^^^^^^^^^^^ +.. automethod:: Transaction.as_tryte_string + +**from_tryte_string** +^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Transaction.from_tryte_string + +**get_signature_validation_trytes** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Transaction.get_signature_validation_trytes ProposedTransaction ~~~~~~~~~~~~~~~~~~~ -``ProposedTransaction`` is a transaction that was created locally and -hasn't been broadcast yet. +.. autoclass:: ProposedTransaction -.. code:: python +**as_tryte_string** +^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedTransaction.as_tryte_string - txn_2 =\ - ProposedTransaction( - address = - Address( - b'TESTVALUE9DONTUSEINPRODUCTION99999XE9IVG' - b'EFNDOCQCMERGUATCIEGGOHPHGFIAQEZGNHQ9W99CH' - ), - - message = TryteString.from_unicode('thx fur cheezburgers'), - tag = Tag(b'KITTEHS'), - value = 42, - ) - -This type is useful when creating new transactions to broadcast to the -Tangle. Note that creating a ``ProposedTransaction`` requires only a -small subset of the attributes needed to create a ``Transaction`` -object. - -To create a ``ProposedTransaction``, specify the following values: - -- ``address: Address``: The address associated with the transaction. - Note that each transaction references exactly one address; in order - to transfer IOTAs from one address to another, you must create at - least two transactions: One to deduct the IOTAs from the sender's - balance, and one to add the IOTAs to the recipient's balance. -- ``message: Optional[TryteString]``: Optional trytes to attach to the - transaction. This could be any value (character strings, binary data, - or raw trytes), as long as it's converted to a ``TryteString`` first. -- ``tag: Optional[Tag]``: Optional tag to classify this transaction. - Each transaction may have exactly one tag, and the tag is limited to - 27 trytes. -- ``value: int``: The number of IOTAs being transferred in this - transaction. This value can be 0; for example, to send a message - without spending any IOTAs. +**increment_legacy_tag** +^^^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedTransaction.increment_legacy_tag Bundle Types ------------ -As with transactions, PyOTA defines two bundle types. +As with transactions, PyOTA defines two different types to represent bundles: + + - :py:class:`Bundle` for bundles that have already been + broadcast to the Tangle. Generally, you will never need to create + :py:class:`Bundle` objects; the API will build them for you, + as the result of various API methods. + - :py:class:`ProposedBundle` for bundles that have been created + locally and have not been broadcast yet. Bundle ~~~~~~ -.. code:: python +.. autoclass:: Bundle + :members: is_confirmed, hash, tail_transaction, transactions - from iota import Bundle +**as_json_compatible** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Bundle.as_json_compatible - bundle = Bundle.from_tryte_strings([ - b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC...', - b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ...', - # etc. - ]) +**as_tryte_strings** +^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Bundle.as_tryte_strings -``Bundle`` represents a bundle of transactions published on the Tangle. -It is intended to be a read-only object, allowing you to inspect the -transactions and bundle metadata. +**from_tryte_strings** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Bundle.from_tryte_strings -Each bundle has the following attributes: +**get_messages** +^^^^^^^^^^^^^^^^ +.. automethod:: Bundle.get_messages -- ``hash: BundleHash``: The hash of this bundle. This value is - generated by taking a hash of the metadata from all transactions in - the bundle. -- ``is_confirmed: Optional[bool]``: Whether the transactions in this - bundle have been confirmed. Refer to the Basic Concepts section for - more information. -- ``tail_transaction: Optional[Transaction]``: The bundle's tail - transaction. -- ``transactions: List[Transaction]``: The transactions associated with - this bundle. +**group_transactions** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Bundle.group_transactions ProposedBundle ~~~~~~~~~~~~~~ +.. note:: + This section contains information about how PyOTA works "under the + hood". + + The :py:meth:`Iota.prepare_transfer` API method encapsulates this + functionality for you; it is not necessary to understand how + :py:class:`ProposedBundle` works in order to use PyOTA. + +.. autoclass:: ProposedBundle + :members: balance, tag + +:py:class:`ProposedBundle` provides a convenient interface for creating new +bundles, listed in the order that they should be invoked: + +**add_transaction** +^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.add_transaction + +**add_inputs** +^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.add_inputs + +**send_unspent_inputs_to** +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.send_unspent_inputs_to + +**add_signature_or_message** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.add_signature_or_message + +**finalize** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.finalize + +**sign_inputs** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.sign_inputs + +**sign_input_at** +^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.sign_input_at + +**as_json_compatible** +^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: ProposedBundle.as_json_compatible + +**Example usage** +^^^^^^^^^^^^^^^^^ .. code:: python @@ -315,41 +380,7 @@ ProposedBundle bundle.finalize() bundle.sign_inputs(KeyGenerator(b'SEED9GOES9HERE')) -.. note:: - - This section contains information about how PyOTA works "under the - hood". - - The ``prepare_transfer`` API method encapsulates this functionality - for you; it is not necessary to understand how ``ProposedBundle`` - works in order to use PyOTA. - - -``ProposedBundle`` provides a convenient interface for creating new -bundles, listed in the order that they should be invoked: - -- ``add_transaction: (ProposedTransaction) -> None``: Adds a - transaction to the bundle. If necessary, it may split the transaction - into multiple (for example, if the transaction's message is too long - to fit into 2187 trytes). -- ``add_inputs: (List[Address]) -> None``: Specifies inputs that can be - used to fund transactions that spend IOTAs. The ``ProposedBundle`` - will use these to create the necessary input transactions. -- You can use the ``get_inputs`` API command to find suitable inputs. -- ``send_unspent_inputs_to: (Address) -> None``: Specifies the address - that will receive unspent IOTAs. The ``ProposedBundle`` will use this - to create the necessary change transaction, if necessary. -- ``add_signature_or_message: (List[Fragment], int) -> None``: - Adds signature or message fragments to transactions in the bundle - starting from ``start_index``. Must be called before the bundle is - finalized. -- ``finalize: () -> None``: Prepares the bundle for PoW. Once this - method is invoked, no new transactions may be added to the bundle. -- ``sign_inputs: (KeyGenerator) -> None``: Generates the necessary - cryptographic signatures to authorize spending the inputs. You do not - need to invoke this method if the bundle does not contain any - transactions that spend IOTAs. - -Once the ``ProposedBundle`` has been finalized (and inputs signed, if -necessary), invoke its ``as_tryte_strings`` method to generate the raw -trytes that should be included in an ``attach_to_tangle`` API request. +Once the :py:class:`ProposedBundle` has been finalized (and inputs signed, if +necessary), invoke its :py:meth:`ProposedBundle.as_tryte_strings` method to +generate the raw trytes that should be included in an +:py:meth:`Iota.attach_to_tangle` API request. diff --git a/iota/__init__.py b/iota/__init__.py index 6a48287..8ab602b 100644 --- a/iota/__init__.py +++ b/iota/__init__.py @@ -38,6 +38,9 @@ from .api import * from .trits import * +# Expose Seed on the top level package +from .crypto.types import Seed + # :see: http://stackoverflow.com/a/2073599/ from pkg_resources import require __version__ = require('PyOTA')[0].version diff --git a/iota/crypto/addresses.py b/iota/crypto/addresses.py index 88eb001..8c335eb 100644 --- a/iota/crypto/addresses.py +++ b/iota/crypto/addresses.py @@ -26,6 +26,24 @@ class AddressGenerator(Iterable[Address]): Note also that :py:meth:`iota.api.IotaApi.get_new_addresses` uses ``AddressGenerator`` internally, so you get the best of both worlds when you use the API (: + + :param TrytesCompatible seed: + The seed to derive addresses from. + + :param int security_level: + When generating a new address, you can specify a security level for it. + The security level of an address affects how long the private key is, + how secure a spent address is against brute-force attacks, and how many + transactions are needed to contain the signature. + + Could be either 1, 2 or 3. + + Reference: + + - https://docs.iota.org/docs/getting-started/0.1/clients/security-levels + + :param bool checksum: + Whether to generate address with or without checksum. """ DEFAULT_SECURITY_LEVEL = 2 """ @@ -90,6 +108,8 @@ def get_addresses(self, start, count=1, step=1): This may be any non-zero (positive or negative) integer. :return: + ``List[Address]`` + Always returns a list, even if only one address is generated. The returned list will contain ``count`` addresses, except diff --git a/iota/crypto/types.py b/iota/crypto/types.py index 6b2664b..e0597db 100644 --- a/iota/crypto/types.py +++ b/iota/crypto/types.py @@ -21,11 +21,20 @@ class Digest(TryteString): """ A private key digest. Basically the same thing as a regular - :py:class:`TryteString`, except that it (usually) has a key index + :py:class:`iota.TryteString`, except that it (usually) has a key index associated with it. Note: in a few cases (e.g., generating multisig addresses), a key index is not necessary/available. + + :param TrytesCompatible trytes: + Byte string or bytearray. + + :param Optional[int] key_index: + Key index used for generating the digest. + + :raises ValueError: + if length of ``trytes`` is not multiple of :py:attr:`iota.Hash.LEN`. """ def __init__(self, trytes, key_index=None): @@ -57,11 +66,26 @@ def security_level(self): """ Returns the number of iterations that were used to generate this digest (also known as "security level"). + + :return: + ``int`` """ return len(self) // Hash.LEN def as_json_compatible(self): # type: () -> dict + """ + Returns a JSON-compatible representation of the digest. + + :return: + ``dict`` with the following structure:: + + { + trytes': Text, + 'key_index': int, + } + + """ return { 'trytes': self._trytes.decode('ascii'), 'key_index': self.key_index, @@ -70,13 +94,19 @@ def as_json_compatible(self): class Seed(TryteString): """ - A TryteString that acts as a seed for crypto functions. + An :py:class:`iota.TryteString` that acts as a seed for crypto functions. - Note: This class is identical to :py:class:`TryteString`, but it has + Note: This class is identical to :py:class:`iota.TryteString`, but it has a distinct type so that seeds can be identified in Python code. IMPORTANT: For maximum security, a seed must be EXACTLY 81 trytes! + :param TrytesCompatible trytes: + Byte string or bytearray. + + :raises Warning: + if ``trytes`` are longer than 81 trytes in length. + References: - https://iota.stackexchange.com/q/249 @@ -101,7 +131,7 @@ def random(cls, length=Hash.LEN): """ Generates a random seed using a CSPRNG. - :param length: + :param int length: Length of seed, in trytes. For maximum security, this should always be set to 81, but @@ -109,14 +139,39 @@ def random(cls, length=Hash.LEN): doing. See https://iota.stackexchange.com/q/249 for more info. + + :return: + :py:class:`iota.Seed` object. + + Example usage:: + + from iota import Seed + + my_seed = Seed.random() + + print(my_seed) + """ return super(Seed, cls).random(length) class PrivateKey(TryteString): """ - A TryteString that acts as a private key, e.g., for generating + An :py:class:`iota.TryteString` that acts as a private key, e.g., for generating message signatures, new addresses, etc. + + :param TrytesCompatible trytes: + Byte string or bytearray. + + :param Optional[int] key_index: + Key index used for generating the private key. + + :param Optional[int] security_level: + Security level used for generating the private key. + + :raises ValueError: + if length of ``trytes`` is not a multiple of + :py:attr:`iota.transaction.Fragement.LEN`. """ def __init__(self, trytes, key_index=None, security_level=None): @@ -143,6 +198,19 @@ def __init__(self, trytes, key_index=None, security_level=None): def as_json_compatible(self): # type: () -> dict + """ + Returns a JSON-compatible representation of the private key. + + :return: + ``dict`` with the following structure:: + + { + trytes': Text, + 'key_index': int, + 'security_level': int, + } + + """ return { 'trytes': self._trytes.decode('ascii'), 'key_index': self.key_index, @@ -160,6 +228,9 @@ def get_digest(self): The digest is essentially the result of running the signing key through a PBKDF, yielding a constant-length hash that can be used for crypto. + + :return: + :py:class:`iota.crypto.types.Digest` object. """ hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN @@ -209,14 +280,21 @@ def sign_input_transactions(self, bundle, start_index): """ Signs the inputs starting at the specified index. - :param bundle: + :param Bundle bundle: The bundle that contains the input transactions to sign. - :param start_index: + :param int start_index: The index of the first input transaction. If necessary, the resulting signature will be split across subsequent transactions automatically. + + :raises ValuError: + - if ``bundle`` is not finalized. + - if attempting to sign non-input transactions. + - if attempting to sign transactions with non-empty + ``signature_message_fragment`` field. + :raises IndexError: if wrong ``start_index`` is provided. """ if not bundle.hash: diff --git a/iota/transaction/base.py b/iota/transaction/base.py index 776dba4..c904c70 100644 --- a/iota/transaction/base.py +++ b/iota/transaction/base.py @@ -23,6 +23,60 @@ class Transaction(JsonSerializable): """ A transaction that has been attached to the Tangle. + + :param Optional[TransactionHash] hash_: + Transaction ID + + :param Optional[Fragment] signature_message_fragment: + Signature or message fragment. + + :param Address address: + The address associated with this transaction. + + :param int value: + Value of the transaction in iotas. Can be negative as well + (spending from address). + + :param int timestamp: + Unix timestamp in seconds. + + :param Optional[int] current_index: + Index of the transaction within the bundle. + + :param Optional[int] last_index: + Index of head transaction in the bundle. + + :param Optional[BundleHash] bundle_hash: + Bundle hash of the bundle containing the transaction. + + :param Optional[TransactionHash] trunk_transaction_hash: + Hash of trunk transaction. + + :param Optional[TransactionHash] branch_transaction_hash: + Hash of branch transaction. + + :param Optional[Tag] tag: + Optional classification tag applied to this transaction. + + :param Optional[int] attachment_timestamp: + Unix timestamp in milliseconds, decribes when the proof-of-work for this + transaction was done. + + :param Optional[int] attachment_timestamp_lower_bound: + Unix timestamp in milliseconds, lower bound of attachment. + + :param Optional[int] attachment_timestamp_upper_bound: + Unix timestamp in milliseconds, upper bound of attachment. + + :param Optional[Nonce] nonce: + Unique value used to increase security of the transaction hash. Result of + the proof-of-work aglorithm. + + :param Optional[Tag] legacy_tag: + Optional classification legacy_tag applied to this transaction. + + :return: + :py:class:`Transaction` object. """ @classmethod @@ -31,14 +85,67 @@ def from_tryte_string(cls, trytes, hash_=None): """ Creates a Transaction object from a sequence of trytes. - :param trytes: + :param TrytesCompatible trytes: Raw trytes. Should be exactly 2673 trytes long. - :param hash_: + :param Optional[TransactionHash] hash_: The transaction hash, if available. If not provided, it will be computed from the transaction trytes. + + :return: + :py:class:`Transaction` object. + + Example usage:: + + from iota import Transaction + + txn =\\ + Transaction.from_tryte_string( + b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' + b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ' + b'WIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTS' + b'VPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRB' + b'XIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRY' + b'WHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9' + b'JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCD' + b'X9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRG' + b'YI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LV' + b'PJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEB' + b'CS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASP' + b'ZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYR' + b'TKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJD' + b'XMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHB' + b'DZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROH' + b'BETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQE' + b'EWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWE' + b'IDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLH' + b'LUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPI' + b'XNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTC' + b'DMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRA' + b'BQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGO' + b'XQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKU' + b'ROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCP' + b'JUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENI' + b'XRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQ' + b'UBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999' + b'999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSK' + b'UCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999' + ) + """ tryte_string = TransactionTrytes(trytes) @@ -98,75 +205,123 @@ def __init__( ): self.hash = hash_ """ - Transaction ID, generated by taking a hash of the transaction - trits. + The transaction hash, used to uniquely identify the transaction on the + Tangle. + + This value is generated by taking a hash of the raw transaction trits. + + :type: :py:class:`TransactionHash` """ self.bundle_hash = bundle_hash """ - Bundle hash, generated by taking a hash of metadata from all the + The bundle hash, used to identify transactions that are part of the same + bundle. + + This value is generated by taking a hash of the metadata from all transactions in the bundle. + + :type: :py:class:`BundleHash` """ self.address = address """ The address associated with this transaction. - If ``value`` is != 0, the associated address' balance is + Depending on the transaction's ``value``, this address may be a sender + or a recipient. If ``value`` is != 0, the associated address' balance is adjusted as a result of this transaction. + + :type: :py:class:`Address` """ self.value = value """ - Amount to adjust the balance of ``address``. - Can be negative (i.e., for spending inputs). + The number of iotas being transferred in this transaction: + + - If this value is negative, then the ``address`` is spending iotas. + - If it is positive, then the ``address`` is receiving iotas. + - If it is zero, then this transaction is being used to carry metadata + (such as a signature fragment or a message) instead of transferring + iotas. + + :type: ``int`` """ self._legacy_tag = legacy_tag """ - Optional classification legacy_tag applied to this transaction. + A short message attached to the transaction. + + .. warning:: + Deprecated, use :py:attr:`Transaction.tag` instead. + + :type: :py:class:`Tag` """ self.nonce = nonce """ Unique value used to increase security of the transaction hash. + + This is the product of the PoW process. + + :type: :py:class:`Nonce` """ self.timestamp = timestamp """ Timestamp used to increase the security of the transaction hash. + Describes when the transaction was created. + .. important:: This value is easy to forge! Do not rely on it when resolving conflicts! + + :type: ``int``, unix timestamp in seconds. """ self.current_index = current_index """ The position of the transaction inside the bundle. + - If the ``current_index`` value is 0, then this is the "head transaction". + - If it is equal to ``last_index``, then this is the "tail transaction". + For value transfers, the "spend" transaction is generally in the 0th position, followed by inputs, and the "change" transaction is last. + + :type: ``int`` """ self.last_index = last_index """ - The position of the final transaction inside the bundle. + The index of the final transaction in the bundle. + + This value is attached to every transaction to make it easier to + traverse and verify bundles. + + :type: ``int`` """ self.trunk_transaction_hash = trunk_transaction_hash """ + The transaction hash of the next transaction in the bundle. + In order to add a transaction to the Tangle, the client must perform PoW to "approve" two existing transactions, called the "trunk" and "branch" transactions. The trunk transaction is generally used to link transactions within a bundle. + + :type: :py:class:`TransactionHash` """ self.branch_transaction_hash = branch_transaction_hash """ + An unrelated transaction that this transaction "approves". + In order to add a transaction to the Tangle, the client must perform PoW to "approve" two existing transactions, called the "trunk" and "branch" transactions. @@ -174,18 +329,41 @@ def __init__( The branch transaction may be selected strategically to maximize the bundle's chances of getting confirmed; otherwise it usually has no significance. + + :type: :py:class:`TransactionHash` """ self.tag = tag """ Optional classification tag applied to this transaction. + + Many transactions have empty tags (``Tag(b'999999999999999999999999999')``). + + :type: :py:class:`Tag` """ self.attachment_timestamp = attachment_timestamp + """ + Estimated epoch time of the attachment to the tangle. + + Decribes when the proof-of-work for this transaction was done. + + :type: ``int``, unix timestamp in milliseconds, + """ self.attachment_timestamp_lower_bound = attachment_timestamp_lower_bound + """ + The lowest possible epoch time of the attachment to the tangle. + + :type: ``int``, unix timestamp in milliseconds. + """ self.attachment_timestamp_upper_bound = attachment_timestamp_upper_bound + """ + The highest possible epoch time of the attachment to the tangle. + + :type: ``int``, unix timestamp in milliseconds. + """ self.signature_message_fragment = signature_message_fragment """ @@ -203,6 +381,8 @@ def __init__( pretty much any value. Like signatures, the message may be split across multiple transactions if it is too large to fit inside a single transaction. + + :type: :py:class:`Fragment` """ self.is_confirmed = None # type: Optional[bool] @@ -210,10 +390,12 @@ def __init__( Whether this transaction has been confirmed by neighbor nodes. Must be set manually via the ``getInclusionStates`` API command. + :type: ``Optional[bool]`` + References: - - :py:meth:`iota.api.StrictIota.get_inclusion_states` - - :py:meth:`iota.api.Iota.get_transfers` + - :py:meth:`Iota.get_inclusion_states` + - :py:meth:`Iota.get_transfers` """ @property @@ -312,6 +494,28 @@ def as_json_compatible(self): """ Returns a JSON-compatible representation of the object. + :return: + ``dict`` with the following structure:: + + { + 'hash_': TransactionHash, + 'signature_message_fragment': Fragment, + 'address': Address, + 'value': int, + 'legacy_tag': Tag, + 'timestamp': int, + 'current_index': int, + 'last_index': int, + 'bundle_hash': BundleHash, + 'trunk_transaction_hash': TransactionHash, + 'branch_transaction_hash': TransactionHash, + 'tag': Tag, + 'attachment_timestamp': int, + 'attachment_timestamp_lower_bound': int, + 'attachment_timestamp_upper_bound': int, + 'nonce': Nonce, + } + References: - :py:class:`iota.json.JsonEncoder`. @@ -344,6 +548,9 @@ def as_tryte_string(self): # type: () -> TransactionTrytes """ Returns a TryteString representation of the transaction. + + :return: + :py:class:`TryteString` object. """ return TransactionTrytes( self.signature_message_fragment @@ -368,6 +575,9 @@ def get_signature_validation_trytes(self): """ Returns the values needed to validate the transaction's ``signature_message_fragment`` value. + + :return: + :py:class:`TryteString` object. """ return ( self.address.address @@ -399,9 +609,16 @@ class Bundle(JsonSerializable, Sequence[Transaction]): Instead, Bundles must be inferred by following linked transactions with the same bundle hash. + :param Optional[Iterable[Transaction]] transactions: + Transactions in the bundle. Note that transactions will be sorted into + ascending order based on their ``current_index``. + + :return: + :py:class:`Bundle` object. + References: - - :py:class:`iota.commands.extended.get_bundles.GetBundlesCommand` + - :py:class:`Iota.get_transfers` """ @classmethod @@ -409,6 +626,26 @@ def from_tryte_strings(cls, trytes): # type: (Iterable[TryteString]) -> Bundle """ Creates a Bundle object from a list of tryte values. + + Note, that this is effectively calling + :py:meth:`Transaction.from_tryte_string` on the iterbale elements and + constructing the bundle from the created transactions. + + :param Iterable[TryteString] trytes: + List of raw transaction trytes. + + :return: + :py:class:`Bundle` object. + + Example usage:: + + from iota import Bundle + bundle = Bundle.from_tryte_strings([ + b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC...', + b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ...', + # etc. + ]) + """ return cls(map(Transaction.from_tryte_string, trytes)) @@ -417,6 +654,9 @@ def __init__(self, transactions=None): super(Bundle, self).__init__() self.transactions = [] # type: List[Transaction] + """ + List of :py:class:`Transaction` objects that are in the bundle. + """ if transactions: self.transactions.extend( sorted(transactions, key=attrgetter('current_index')), @@ -429,7 +669,7 @@ def __init__(self, transactions=None): References: - - :py:class:`iota.commands.extended.get_transfers.GetTransfersCommand` + - :py:class:`Iota.get_transfers` """ def __contains__(self, transaction): @@ -457,9 +697,11 @@ def is_confirmed(self): This attribute must be set manually. + :return: ``bool`` + References: - - :py:class:`iota.commands.extended.get_transfers.GetTransfersCommand` + - :py:class:`Iota.get_transfers` """ return self._is_confirmed @@ -483,7 +725,9 @@ def hash(self): This value is determined by inspecting the bundle's tail transaction, so in a few edge cases, it may be incorrect. - If the bundle has no transactions, this method returns ``None``. + :return: + - :py:class:`BundleHash` object, or + - If the bundle has no transactions, this method returns ``None``. """ try: return self.tail_transaction.bundle_hash @@ -495,6 +739,8 @@ def tail_transaction(self): # type: () -> Transaction """ Returns the tail transaction of the bundle. + + :return: :py:class:`Transaction` """ return self[0] @@ -504,7 +750,7 @@ def get_messages(self, errors='drop'): Attempts to decipher encoded messages from the transactions in the bundle. - :param errors: + :param Text errors: How to handle trytes that can't be converted, or bytes that can't be decoded using UTF-8: @@ -519,6 +765,8 @@ def get_messages(self, errors='drop'): 'ignore' Omit the invalid tryte/byte sequence. + + :return: ``List[Text]`` """ decode_errors = 'strict' if errors == 'drop' else errors @@ -548,7 +796,7 @@ def as_tryte_strings(self, head_to_tail=False): Returns TryteString representations of the transactions in this bundle. - :param head_to_tail: + :param bool head_to_tail: Determines the order of the transactions: - ``True``: head txn first, tail txn last. @@ -556,6 +804,8 @@ def as_tryte_strings(self, head_to_tail=False): Note that the order is reversed by default, as this is the way bundles are typically broadcast to the Tangle. + + :return: ``List[TransactionTrytes]`` """ transactions = self if head_to_tail else reversed(self) return [t.as_tryte_string() for t in transactions] @@ -565,6 +815,10 @@ def as_json_compatible(self): """ Returns a JSON-compatible representation of the object. + :return: + ``List[dict]``. The ``dict`` list elements contain individual + transactions as in :py:meth:`Transaction.as_json_compatible`. + References: - :py:class:`iota.json.JsonEncoder`. @@ -575,6 +829,9 @@ def group_transactions(self): # type: () -> List[List[Transaction]] """ Groups transactions in the bundle by address. + + :return: + ``List[List[Transaction]]`` """ groups = [] diff --git a/iota/transaction/creation.py b/iota/transaction/creation.py index edc900e..302e5f8 100644 --- a/iota/transaction/creation.py +++ b/iota/transaction/creation.py @@ -28,8 +28,52 @@ class ProposedTransaction(Transaction): """ A transaction that has not yet been attached to the Tangle. - Provide to :py:meth:`iota.api.Iota.send_transfer` to attach to - tangle and publish/store. + Proposed transactions are created locally. Note that for creation, only a + small subset of the :py:class:`Transaction` attributes is needed. + + Provide to :py:meth:`Iota.send_transfer` to attach to tangle and + publish/store. + + .. note:: + In order to follow naming convention of other libs, you may use the + name ``Transfer`` interchangeably with ``ProposedTransaction``. + See https://github.com/iotaledger/iota.py/issues/72 for more info. + + :param Address address: + Address associated with the transaction. + + :param int value: + Transaction value. + + :param Optional[Tag] tag: + Optional classification tag applied to this transaction. + + :param Optional[TryteString] message: + Message to be included in + :py:attr:`transaction.Transaction.signature_or_message_fragment` field + of the transaction. Should not be longer than + :py:attr:`transaction.Fragment.LEN`. + + :param Optional[int] timestamp: + Timestamp of transaction creation. If not supplied, the library will + generate it. + + :return: + :py:class:`iota.ProposedTransaction` object. + + Example usage:: + + txn=\\ + ProposedTransaction( + address = + Address( + b'TESTVALUE9DONTUSEINPRODUCTION99999XE9IVG' + b'EFNDOCQCMERGUATCIEGGOHPHGFIAQEZGNHQ9W99CH' + ), + message = TryteString.from_unicode('thx fur cheezburgers'), + tag = Tag(b'KITTENS'), + value = 42, + ) """ def __init__( @@ -73,6 +117,13 @@ def as_tryte_string(self): # type: () -> TryteString """ Returns a TryteString representation of the transaction. + + :return: + :py:class:`TryteString` object. + + :raises RuntimeError: + if the transaction doesn't have a bundle hash field, meaning that + the bundle containing the transaction hasn't been finalized yet. """ if not self.bundle_hash: raise with_context( @@ -116,6 +167,25 @@ class ProposedBundle(Bundle, Sequence[ProposedTransaction]): """ A collection of proposed transactions, to be treated as an atomic unit when attached to the Tangle. + + :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. + If provided, the library will create and sign withdrawing transactions + from these addresses. + + See :py:meth:`Iota.get_inputs` for more info. + + :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, or to a newly-generated and unused + address if not. + + :return: :py:class:`ProposedBundle` """ def __init__( @@ -141,6 +211,8 @@ def __bool__(self): # type: () -> bool """ Returns whether this bundle has any transactions. + + :return: ``bool`` """ return bool(self._transactions) @@ -187,6 +259,8 @@ def balance(self): - A negative balance means that there are unspent inputs; use :py:meth:`send_unspent_inputs_to` to send the unspent inputs to a "change" address. + + :return: ``bool`` """ return sum(t.value for t in self._transactions) @@ -195,6 +269,8 @@ def tag(self): # type: () -> Tag """ Determines the most relevant tag for the bundle. + + :return: :py:class:`transaction.Tag` """ for txn in reversed(self): # type: ProposedTransaction if txn.tag: @@ -207,6 +283,10 @@ def as_json_compatible(self): """ Returns a JSON-compatible representation of the object. + :return: + ``List[dict]``. The ``dict`` list elements contain individual + transactions as in :py:meth:`ProposedTransaction.as_json_compatible`. + References: - :py:class:`iota.json.JsonEncoder`. @@ -220,6 +300,14 @@ def add_transaction(self, transaction): If the transaction message is too long, it will be split automatically into multiple transactions. + + :param ProposedTransaction transaction: + The transaction to be added. + + :raises RuntimeError: if bundle is already finalized + :raises ValueError: + if trying to add a spending transaction. Use :py:meth:`add_inputs` + instead. """ if self.hash: raise RuntimeError('Bundle is already finalized.') @@ -253,17 +341,26 @@ def add_transaction(self, transaction): def add_inputs(self, inputs): # type: (Iterable[Address]) -> None """ - Adds inputs to spend in the bundle. + Specifies inputs that can be used to fund transactions that spend iotas. + + The :py:class:`ProposedBundle` will use these to create the necessary + input transactions. Note that each input may require multiple transactions, in order to hold the entire signature. - :param inputs: + :param Iterable[Address] inputs: Addresses to use as the inputs for this bundle. .. important:: Must have ``balance`` and ``key_index`` attributes! - Use :py:meth:`iota.api.get_inputs` to prepare inputs. + Use :py:meth:`Iota.get_inputs` to prepare inputs. + + :raises RuntimeError: if bundle is already finalized. + :raises ValueError: + - if input address has no ``balance``. + - if input address has no ``key_index``. + """ if self.hash: raise RuntimeError('Bundle is already finalized.') @@ -302,10 +399,17 @@ def add_inputs(self, inputs): def send_unspent_inputs_to(self, address): # type: (Address) -> None """ - Adds a transaction to send "change" (unspent inputs) to the - specified address. + Specifies the address that will receive unspent iotas. + + The :py:class:`ProposedBundle` will use this to create the necessary + change transaction, if necessary. If the bundle has no unspent inputs, this method does nothing. + + :param Address address: + Address to send unspent inputs to. + + :raises RuntimeError: if bundle is already finalized. """ if self.hash: raise RuntimeError('Bundle is already finalized.') @@ -316,6 +420,20 @@ def finalize(self): # type: () -> None """ Finalizes the bundle, preparing it to be attached to the Tangle. + + This operation includes checking if the bundle has zero balance, + generating the bundle hash and updating the transactions with it, + furthermore to initialize signature/message fragment fields. + + Once this method is invoked, no new transactions may be added to the + bundle. + + :raises RuntimeError: if bundle is already finalized. + :raises ValueError: + - if bundle has no transactions. + - if bundle has unspent inputs (there is no ``change_address`` + attribute specified.) + - if inputs are insufficient to cover bundle spend. """ if self.hash: raise RuntimeError('Bundle is already finalized.') @@ -387,6 +505,23 @@ def sign_inputs(self, key_generator): # type: (KeyGenerator) -> None """ Sign inputs in a finalized bundle. + + Generates the necessary cryptographic signatures to authorize spending + the inputs. + + .. note:: + You do not need to invoke this method if the bundle does + not contain any transactions that spend iotas. + + :param KeyGenerator key_generator: + Generator to create private keys for signing. + + :raises RuntimeError: if bundle is not yet finalized. + :raises ValueError: + - if the input transaction specifies an address that doesn't have + ``key_index`` attribute defined. + - if the input transaction specifies an address that doesn't have + ``security_level`` attribute defined. """ if not self.hash: raise RuntimeError('Cannot sign inputs until bundle is finalized.') @@ -442,7 +577,7 @@ def sign_input_at(self, start_index, private_key): """ Signs the input at the specified index. - :param start_index: + :param int start_index: The index of the first input transaction. If necessary, the resulting signature will be split across @@ -450,13 +585,15 @@ def sign_input_at(self, start_index, private_key): ``security_level=2``, you still only need to call :py:meth:`sign_input_at` once). - :param private_key: + :param PrivateKey private_key: The private key that will be used to generate the signature. .. important:: Be sure that the private key was generated using the correct seed, or the resulting signature will be invalid! + + :raises RuntimeError: if bundle is not yet finalized. """ if not self.hash: raise RuntimeError('Cannot sign inputs until bundle is finalized.') @@ -467,6 +604,9 @@ def _create_input_transactions(self, addy): # type: (Address) -> None """ Creates transactions for the specified input address. + + :param Address addy: + Input address. """ self._transactions.append(ProposedTransaction( address=addy, @@ -510,6 +650,15 @@ def add_signature_or_message( :param int start_index: Index of transaction in bundle from where addition shoudl start. + + :raise RuntimeError: if bundle is already finalized. + :raise ValueError: + - if empty list is provided for ``fragments`` + - if wrong ``start_index`` is provided. + - if ``fragments`` is too long and does't fit into the bundle. + :raise TypeError: + - if ``fragments`` is not an ``Iterable`` + - if ``fragments`` contains other types than :py:class:`Fragment`. """ if self.hash: raise RuntimeError('Bundle is already finalized.') diff --git a/iota/transaction/types.py b/iota/transaction/types.py index a76bea1..caba973 100644 --- a/iota/transaction/types.py +++ b/iota/transaction/types.py @@ -17,21 +17,24 @@ class BundleHash(Hash): """ - A TryteString that acts as a bundle hash. + An :py:class:`TryteString` (:py:class:`Hash`) that acts as a bundle hash. """ pass class TransactionHash(Hash): """ - A TryteString that acts as a transaction hash. + An :py:class:`TryteString` (:py:class:`Hash`) that acts as a transaction hash. """ pass class Fragment(TryteString): """ - A signature/message fragment in a transaction. + An :py:class:`TryteString` representation of a signature/message fragment + in a transaction. + + :raises ValueError: if ``trytes`` is longer than 2187 trytes in length. """ LEN = FRAGMENT_LENGTH """ @@ -57,7 +60,9 @@ def __init__(self, trytes): class TransactionTrytes(TryteString): """ - A TryteString representation of a Transaction. + An :py:class:`TryteString` representation of a Transaction. + + :raises ValueError: if ``trytes`` is longer than 2673 trytes in length. """ LEN = 2673 """ @@ -83,7 +88,9 @@ def __init__(self, trytes): class Nonce(TryteString): """ - A TryteString that acts as a transaction nonce. + An :py:class:`TryteString` that acts as a transaction nonce. + + :raises ValueError: if ``trytes`` is longer than 27 trytes in length. """ LEN = 27 """ diff --git a/iota/types.py b/iota/types.py index beeab33..5f5567c 100644 --- a/iota/types.py +++ b/iota/types.py @@ -40,13 +40,31 @@ class TryteString(JsonSerializable): """ A string representation of a sequence of trytes. - A tryte string is similar in concept to Python's byte string, except - it has a more limited alphabet. Byte strings are limited to ASCII - (256 possible values), while the tryte string alphabet only has 27 - characters (one for each possible tryte configuration). + A :py:class:`TryteString` is an ASCII representation of a sequence of trytes. + In many respects, it is similar to a Python ``bytes`` object (which is an + ASCII representation of a sequence of bytes). + + In fact, the two objects behave very similarly; they support + concatenation, comparison, can be used as dict keys, etc. + + However, unlike ``bytes``, a :py:class:`TryteString` can only contain + uppercase letters and the number 9 (as a regular expression: ``^[A-Z9]*$``). .. important:: A TryteString does not represent a numeric value! + + :param TrytesCompatible trytes: + Byte string or bytearray. + + :param Optional[int] pad: + Ensure at least this many trytes. + + If there are too few, null trytes will be appended to the + TryteString. + + .. note:: + If the TryteString is too long, it will *not* be + truncated! """ @classmethod @@ -55,8 +73,11 @@ def random(cls, length): """ Generates a random sequence of trytes. - :param length: + :param int length: Number of trytes to generate. + + :return: + :py:class:`TryteString` object. """ alphabet = list(itervalues(AsciiTrytesCodec.alphabet)) generator = SystemRandom() @@ -77,12 +98,13 @@ def from_bytes(cls, bytes_, codec=AsciiTrytesCodec.name, *args, **kwargs): """ Creates a TryteString from a sequence of bytes. - :param bytes_: - Source bytes. + :param Union[binary_type,bytearray] bytes\_: + Source bytes. ASCII representation of a sequence of bytes. + Note that only tryte alphabet supported! - :param codec: + :param Text codec: Reserved for future use. - + Currently supports only the 'trytes_ascii' codec. See https://github.com/iotaledger/iota.py/issues/62 for more information. @@ -91,6 +113,15 @@ def from_bytes(cls, bytes_, codec=AsciiTrytesCodec.name, *args, **kwargs): :param kwargs: Additional keyword arguments to pass to the initializer. + + :return: + :py:class:`TryteString` object. + + Example usage:: + + from iota import TryteString + message_trytes = TryteString.from_bytes(b'HELLO999IOTA') + """ return cls(encode(bytes_, codec), *args, **kwargs) @@ -100,14 +131,23 @@ def from_unicode(cls, string, *args, **kwargs): """ Creates a TryteString from a Unicode string. - :param string: - Source string. + :param Text string: + Source Unicode string. :param args: Additional positional arguments to pass to the initializer. :param kwargs: Additional keyword arguments to pass to the initializer. + + :return: + :py:class:`TryteString` object. + + Example usage:: + + from iota import TryteString + message_trytes = TryteString.from_unicode('Hello, IOTA!') + """ return cls.from_bytes( bytes_=string.encode('utf-8'), @@ -138,7 +178,7 @@ def from_trytes(cls, trytes, *args, **kwargs): """ Creates a TryteString from a sequence of trytes. - :param trytes: + :param Iterable[Iterable[int]] trytes: Iterable of tryte values. In this context, a tryte is defined as a list containing 3 @@ -150,6 +190,25 @@ def from_trytes(cls, trytes, *args, **kwargs): :param kwargs: Additional keyword arguments to pass to the initializer. + :return: + :py:class:`TryteString` object. + + Example usage:: + + from iota import TryteString + message_trytes = TryteString.from_trytes( + [ + [1, 0, -1], + [-1, 1, 0], + [1, -1, 0], + [-1, 1, 0], + [0, 1, 0], + [0, 1, 0], + [-1, 1, 1], + [-1, 1, 0], + ] + ) + References: - :py:meth:`as_trytes` @@ -173,7 +232,7 @@ def from_trits(cls, trits, *args, **kwargs): """ Creates a TryteString from a sequence of trits. - :param trits: + :param Iterable[int] trits: Iterable of trit values (-1, 0, 1). :param args: @@ -182,6 +241,16 @@ def from_trits(cls, trits, *args, **kwargs): :param kwargs: Additional keyword arguments to pass to the initializer. + :return: + :py:class:`TryteString` object. + + Example usage:: + + from iota import TryteString + message_trytes = TryteString.from_trits( + [1, 0, -1, -1, 1, 0, 1, -1, 0, -1, 1, 0, 0, 1, 0, 0, 1, 0, -1, 1, 1, -1, 1, 0] + ) + References: - :py:func:`int_from_trits` @@ -206,10 +275,10 @@ def from_trits(cls, trits, *args, **kwargs): def __init__(self, trytes, pad=None): # type: (TrytesCompatible, Optional[int]) -> None """ - :param trytes: + :param TrytesCompatible trytes: Byte string or bytearray. - :param pad: + :param Optional[int] pad: Ensure at least this many trytes. If there are too few, null trytes will be appended to the @@ -484,7 +553,7 @@ def encode(self, errors='strict', codec=AsciiTrytesCodec.name): Encodes the TryteString into a lower-level primitive (usually bytes). - :param errors: + :param Text errors: How to handle trytes that can't be converted: 'strict' @@ -496,15 +565,40 @@ def encode(self, errors='strict', codec=AsciiTrytesCodec.name): 'ignore' omit the tryte from the result. - :param codec: + :param Text codec: Reserved for future use. See https://github.com/iotaledger/iota.py/issues/62 for more information. - :raise: + :raises: - :py:class:`iota.codecs.TrytesDecodeError` if the trytes cannot be decoded into bytes. + + :return: + Python ``bytes`` object. + + Example usage:: + + from iota import TryteString + + # Message payload as unicode string + message = 'Hello, iota!' + + # Create TryteString + message_trytes = TryteString.from_unicode(message) + + # Encode TryteString into bytes + encoded_message_bytes = message_trytes.encode() + + # This will be b'Hello, iota' + print(encoded_message_bytes) + + # Get the original message + decoded = encoded_message_bytes.decode() + + print(decoded == message) + """ # Converting ASCII-encoded trytes into bytes is considered to be # a *decode* operation according to @@ -533,7 +627,7 @@ def decode(self, errors='strict', strip_padding=True): Decodes the TryteString into a higher-level abstraction (usually Unicode characters). - :param errors: + :param Text errors: How to handle trytes that can't be converted, or bytes that can't be decoded using UTF-8: @@ -546,14 +640,26 @@ def decode(self, errors='strict', strip_padding=True): 'ignore' omit the invalid tryte/byte sequence. - :param strip_padding: + :param bool strip_padding: Whether to strip trailing null trytes before converting. - :raise: + :raises: - :py:class:`iota.codecs.TrytesDecodeError` if the trytes cannot be decoded into bytes. - :py:class:`UnicodeDecodeError` if the resulting bytes cannot be decoded using UTF-8. + + :return: + ``Unicode string`` object. + + Example usage:: + + from iota import TryteString + + trytes = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA') + + message = trytes.decode() + """ trytes = self._trytes if strip_padding and (trytes[-1] == ord(b'9')): @@ -586,6 +692,18 @@ def as_json_compatible(self): References: - :py:class:`iota.json.JsonEncoder`. + + :return: + JSON-compatible representation of the object (string). + + Example usage:: + + from iota import TryteString + + trytes = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA') + + json_payload = trytes.as_json_compatible() + """ return self._trytes.decode('ascii') @@ -595,6 +713,22 @@ def as_integers(self): Converts the TryteString into a sequence of integers. Each integer is a value between -13 and 13. + + See the + `tryte alphabet `_ + for more info. + + :return: + ``List[int]`` + + Example usage:: + + from iota import TryteString + + trytes = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA') + + tryte_ints = trytes.as_integers() + """ return [ self._normalize(AsciiTrytesCodec.index[c]) @@ -613,6 +747,18 @@ def as_trytes(self): .. important:: :py:class:`TryteString` is not a numeric type, so the result of this method should not be interpreted as an integer! + + :return: + ``List[List[int]]`` + + Example usage:: + + from iota import TryteString + + trytes = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA') + + tryte_list = trytes.as_trytes() + """ return [ trits_from_int(n, pad=3) @@ -633,6 +779,18 @@ def as_trits(self): .. important:: :py:class:`TryteString` is not a numeric type, so the result of this method should not be interpreted as an integer! + + :return: + ``List[int]`` + + Example usage:: + + from iota import TryteString + + trytes = TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA') + + trits = trytes.as_trits() + """ # http://stackoverflow.com/a/952952/5568265#comment4204394_952952 return list(chain.from_iterable(self.as_trytes())) @@ -710,7 +868,7 @@ def __next__(self): """ Returns the next chunk in the iterator. - :raise: + :raises: - :py:class:`StopIteration` if there are no more chunks available. """ @@ -731,10 +889,19 @@ def __next__(self): class Hash(TryteString): """ - A TryteString that is exactly one hash long. + A :py:class:`TryteString` that is exactly one hash long. + + :param TrytesCompatible trytes: + Object to construct the hash from. + + :raises ValueError: if ``trytes`` is longer than 81 trytes. + """ # Divide by 3 to convert trits to trytes. LEN = HASH_LENGTH // TRITS_PER_TRYTE + """ + Length is always 81 trytes long. + """ def __init__(self, trytes): # type: (TrytesCompatible) -> None @@ -755,10 +922,31 @@ def __init__(self, trytes): class Address(TryteString): """ - A TryteString that acts as an address, with support for generating + A :py:class:`TryteString` that acts as an address, with support for generating and validating checksums. + + :param TrytesCompatible trytes: + Object to construct the address from. + + :param Optional[int] balance: + Known balance of the address. + + :param Optional[int] key_index: + Index of the address that was used during address generation. + Must be greater than zero. + + :param Optional[int] security_level: + Security level that was used during address generation. + Might be 1, 2 or 3. + + :raises + ValueError: if ``trytes`` is longer than 81 trytes, unless it is + exactly 90 trytes long (address + checksum). """ LEN = Hash.LEN + """ + Length of an address. + """ def __init__( self, @@ -794,6 +982,9 @@ def __init__( # Make the address sans checksum accessible. self.address = self[:self.LEN] # type: TryteString + """ + Address trytes without the checksum. + """ self.balance = balance """ @@ -802,14 +993,14 @@ def __init__( References: - - :py:class:`iota.commands.extended.get_inputs` + - :py:meth:`Iota.get_inputs` - :py:meth:`ProposedBundle.add_inputs` """ self.key_index = key_index """ Index of the key used to generate this address. - Defaults to ``None``; usually set via ``AddressGenerator``. + Defaults to ``None``; usually set via :py:class:`AddressGenerator`. References: @@ -823,6 +1014,32 @@ def __init__( """ def as_json_compatible(self): + """ + Returns a JSON-compatible representation of the Address. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': Text, + 'balance': int, + 'key_index': int, + 'security_level': int, + } + + Example usage:: + + from iota import Address + + # Example address only, do not use in your code! + addy = Address( + b'LVHHIXQNYKWQMGXGLFOKOCDFHPKXAUKWMSZVDRAT' + b'TICUZXFACM9DNJELJGMLMK99KDVVOOWLINVBZIGWZ' + ) + + print(addy.as_json_compatible()) + + """ # type: () -> dict return { 'trytes': self._trytes.decode('ascii'), @@ -835,6 +1052,28 @@ def is_checksum_valid(self): # type: () -> bool """ Returns whether this address has a valid checksum. + + :return: + ``bool`` + + Example usage:: + + from iota import Address + + # Example address only, do not use in your code! + addy = Address( + b'LVHHIXQNYKWQMGXGLFOKOCDFHPKXAUKWMSZVDRAT' + b'TICUZXFACM9DNJELJGMLMK99KDVVOOWLINVBZIGWZ' + ) + + # Should be ``False`` + print(addy.is_checksum_valid()) + + addy.add_checksum() + + # Should be ``True`` + print(addy.is_checksum_valid()) + """ if self.checksum: return self.checksum == self._generate_checksum() @@ -845,6 +1084,27 @@ def with_valid_checksum(self): # type: () -> Address """ Returns the address with a valid checksum attached. + + :return: + :py:class:`Address` object. + + Example usage:: + + from iota import Address + + # Example address only, do not use in your code! + addy = Address( + b'LVHHIXQNYKWQMGXGLFOKOCDFHPKXAUKWMSZVDRAT' + b'TICUZXFACM9DNJELJGMLMK99KDVVOOWLINVBZIGWZ' + ) + + addy_with_checksum = addy.with_valid_checksum() + + print(addy_with_checksum) + + # Should be ``True`` + print(addy_with_checksum.is_checksum_valid()) + """ return Address( trytes=self.address + self._generate_checksum(), @@ -873,7 +1133,32 @@ def _generate_checksum(self): def add_checksum(self): # type: () -> None """ - Add checksum to :py:class:`Address` object. + Adds checksum to :py:class:`Address` object. + + :return: ``None`` + + Example usage:: + + from iota import Address + + # Example address only, do not use in your code! + addy = Address( + b'LVHHIXQNYKWQMGXGLFOKOCDFHPKXAUKWMSZVDRAT' + b'TICUZXFACM9DNJELJGMLMK99KDVVOOWLINVBZIGWZ' + ) + + # Should be ``False`` + print(addy.is_checksum_valid()) + + print(addy.checksum) + + addy.add_checksum() + + # Should be ``True`` + print(addy.is_checksum_valid()) + + print(addy.checksum) + """ if self.is_checksum_valid(): # Address already has a valid checksum. @@ -888,16 +1173,50 @@ def add_checksum(self): def remove_checksum(self): # type: () -> None """ - Remove checksum from :py:class:`Address` object. + Removes checksum from :py:class:`Address` object. + + :return: ``None`` + + Example usage:: + + from iota import Address + + # Example address only, do not use in your code! + addy = Address( + b'LVHHIXQNYKWQMGXGLFOKOCDFHPKXAUKWMSZVDRAT' + b'TICUZXFACM9DNJELJGMLMK99KDVVOOWLINVBZIGWZ' + b'AACAMCWUW' # 9 checksum trytes + ) + + # Should be ``True`` + print(addy.is_checksum_valid()) + + print(addy.checksum) + + addy.remove_checksum() + + # Should be ``False`` + print(addy.is_checksum_valid()) + + print(addy.checksum) + """ self.checksum = None self._trytes = self._trytes[:self.LEN] class AddressChecksum(TryteString): """ - A TryteString that acts as an address checksum. + A :py:class:`TryteString` that acts as an address checksum. + + :param TrytesCompatible trytes: + Checksum trytes. + + :raises ValueError: if ``trytes`` is not exactly 9 trytes in length. """ LEN = 9 + """ + Length of an address checksum. + """ def __init__(self, trytes): # type: (TrytesCompatible) -> None @@ -921,8 +1240,16 @@ def __init__(self, trytes): class Tag(TryteString): """ A TryteString that acts as a transaction tag. + + :param TrytesCompatible trytes: + Tag trytes. + + :raises ValueError: if ``trytes`` is longer than 27 trytes in length. """ LEN = 27 + """ + Length of a tag. + """ def __init__(self, trytes): # type: (TrytesCompatible) -> None