Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
name = 'PyOTA',
description = 'IOTA API library for Python',
url = 'https://github.com/iotaledger/iota.lib.py',
version = '1.0.0b4',
version = '1.0.0b5',

packages = find_packages('src'),
include_package_data = True,
Expand Down
9 changes: 5 additions & 4 deletions src/iota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,22 +461,23 @@ def get_latest_inclusion(self, hashes):
"""
return self.getLatestInclusion(hashes=hashes)

def get_new_addresses(self, index=None, count=1):
# type: (Optional[int], Optional[int]) -> List[Address]
def get_new_addresses(self, index=0, count=1):
# type: (int, Optional[int]) -> List[Address]
"""
Generates one or more new addresses from the seed.

:param index:
Specify the index of the new address (must be >= 1).

If not provided, the address will be generated deterministically.

:param count:
Number of addresses to generate (must be >= 1).

Note: This is more efficient than calling ``get_new_address``
inside a loop.

If ``None``, this method will scan the Tangle to find the next
available unused address and return that.

:return:
List of generated addresses.

Expand Down
2 changes: 1 addition & 1 deletion src/iota/commands/extended/get_new_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def _execute(self, request):
index = request.get('index')

# Required parameters.
seed = request['seed']
seed = request['seed']

generator = AddressGenerator(seed)

Expand Down
4 changes: 2 additions & 2 deletions src/iota/crypto/signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from iota import TRITS_PER_TRYTE, TryteString, TrytesCompatible, Hash
from iota.crypto import Curl, FRAGMENT_LENGTH, HASH_LENGTH
from iota.crypto.types import PrivateKey
from iota.crypto.types import PrivateKey, Seed
from iota.exceptions import with_context

__all__ = [
Expand Down Expand Up @@ -68,7 +68,7 @@ def __init__(self, seed):
# type: (TrytesCompatible) -> None
super(KeyGenerator, self).__init__()

self.seed = TryteString(seed)
self.seed = Seed(seed)

def get_keys(self, start, count=1, step=1, iterations=1):
# type: (int, int, int, int) -> List[PrivateKey]
Expand Down
16 changes: 10 additions & 6 deletions src/iota/crypto/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,15 @@ def get_digest_trits(self):
"""
hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN

digest = [0] * HASH_LENGTH
key_chunks = self.iter_chunks(FRAGMENT_LENGTH)

for (i, fragment) in enumerate(self.iter_chunks(FRAGMENT_LENGTH)): # type: Tuple[int, TryteString]
fragment_start = i * FRAGMENT_LENGTH
fragment_end = fragment_start + FRAGMENT_LENGTH
fragment_trits = fragment[fragment_start:fragment_end].as_trits()
# The digest will contain one hash per key fragment.
digest = [0] * HASH_LENGTH * len(key_chunks)

for (i, fragment) in enumerate(key_chunks): # type: Tuple[int, TryteString]
fragment_trits = fragment.as_trits()

key_fragment = [0] * len(fragment_trits)
key_fragment = [0] * FRAGMENT_LENGTH
hash_trits = []

for j in range(hashes_per_fragment):
Expand All @@ -113,6 +114,9 @@ def get_digest_trits(self):
sponge.absorb(key_fragment)
sponge.squeeze(hash_trits)

fragment_start = i * FRAGMENT_LENGTH
fragment_end = fragment_start + FRAGMENT_LENGTH

digest[fragment_start:fragment_end] = hash_trits

return digest
109 changes: 71 additions & 38 deletions src/iota/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
validate_signature_fragments
from iota.exceptions import with_context
from iota.json import JsonSerializable
from six import PY2

__all__ = [
'Bundle',
Expand Down Expand Up @@ -127,51 +128,51 @@ def __init__(
branch_transaction_hash,
nonce,
):
# type: (Optional[TransactionHash], Optional[TryteString], Address, int, Tag, int, Optional[int], Optional[int], Optional[BundleHash], Optional[TransactionHash], Optional[TransactionHash], Optional[Hash]) -> None
self.hash = hash_
# type: (Optional[TransactionHash], Optional[Fragment], Address, int, Optional[Tag], int, Optional[int], Optional[int], Optional[BundleHash], Optional[TransactionHash], Optional[TransactionHash], Optional[Hash]) -> None
self.hash = hash_ # type: Optional[TransactionHash]
"""
Transaction ID, generated by taking a hash of the transaction
trits.
"""

self.bundle_hash = bundle_hash
self.bundle_hash = bundle_hash # type: Optional[BundleHash]
"""
Bundle hash, generated by taking a hash of metadata from all the
transactions in the bundle.
"""

self.address = address
self.address = address # type: Address
"""
The address associated with this transaction.
If ``value`` is != 0, the associated address' balance is adjusted
as a result of this transaction.
"""

self.value = value
self.value = value # type: int
"""
Amount to adjust the balance of ``address``.
Can be negative (i.e., for spending inputs).
"""

self.tag = tag
self.tag = tag # type: Optional[Tag]
"""
Optional classification tag applied to this transaction.
"""

self.nonce = nonce
self.nonce = nonce # type: Optional[Hash]
"""
Unique value used to increase security of the transaction hash.
"""

self.timestamp = timestamp
self.timestamp = timestamp # type: int
"""
Timestamp used to increase the security of the transaction hash.

IMPORTANT: This value is easy to forge!
Do not rely on it when resolving conflicts!
"""

self.current_index = current_index
self.current_index = current_index # type: Optional[int]
"""
The position of the transaction inside the bundle.

Expand All @@ -180,12 +181,12 @@ def __init__(
last.
"""

self.last_index = last_index
self.last_index = last_index # type: Optional[int]
"""
The position of the final transaction inside the bundle.
"""

self.trunk_transaction_hash = trunk_transaction_hash
self.trunk_transaction_hash = trunk_transaction_hash # type: Optional[TransactionHash]
"""
In order to add a transaction to the Tangle, you must perform PoW
to "approve" two existing transactions, called the "trunk" and
Expand All @@ -195,7 +196,7 @@ def __init__(
a bundle.
"""

self.branch_transaction_hash = branch_transaction_hash
self.branch_transaction_hash = branch_transaction_hash # type: Optional[TransactionHash]
"""
In order to add a transaction to the Tangle, you must perform PoW
to "approve" two existing transactions, called the "trunk" and
Expand All @@ -204,7 +205,7 @@ def __init__(
The branch transaction generally has no significance.
"""

self.signature_message_fragment = signature_message_fragment
self.signature_message_fragment = signature_message_fragment # type: Optional[Fragment]
"""
"Signature/Message Fragment" (note the slash):

Expand Down Expand Up @@ -676,19 +677,34 @@ class ProposedBundle(JsonSerializable, Sequence[ProposedTransaction]):
A collection of proposed transactions, to be treated as an atomic
unit when attached to the Tangle.
"""
def __init__(self, transactions=None):
# type: (Optional[Iterable[ProposedTransaction]]) -> None
def __init__(self, transactions=None, inputs=None, change_address=None):
# type: (Optional[Iterable[ProposedTransaction]], Optional[Iterable[Address]], Optional[Address]) -> None
super(ProposedBundle, self).__init__()

self.hash = None # type: Optional[Hash]
self.tag = None # type: Optional[Tag]

self._transactions = [] # type: List[ProposedTransaction]

if transactions:
for t in transactions:
self.add_transaction(t)

if inputs:
self.add_inputs(inputs)

self.change_address = change_address

def __bool__(self):
# type: () -> bool
"""
Returns whether this bundle has any transactions.
"""
return bool(self._transactions)

# :bc: Magic methods have different names in Python 2.
if PY2:
__nonzero__ = __bool__

def __contains__(self, transaction):
# type: (ProposedTransaction) -> bool
return transaction in self._transactions
Expand Down Expand Up @@ -730,6 +746,19 @@ def balance(self):
"""
return sum(t.value for t in self._transactions)

@property
def tag(self):
# type: () -> Tag
"""
Determines the most relevant tag for the bundle.
"""
for txn in reversed(self): # type: ProposedTransaction
if txn.tag:
# noinspection PyTypeChecker
return txn.tag

return Tag(b'')

def as_json_compatible(self):
# type: () -> List[dict]
"""
Expand Down Expand Up @@ -762,6 +791,9 @@ def add_transaction(self, transaction):
if self.hash:
raise RuntimeError('Bundle is already finalized.')

if transaction.value < 0:
raise ValueError('Use ``add_inputs`` to add inputs to the bundle.')

self._transactions.append(ProposedTransaction(
address = transaction.address,
value = transaction.value,
Expand All @@ -770,9 +802,6 @@ def add_transaction(self, transaction):
timestamp = transaction.timestamp,
))

# Last-added transaction determines the bundle tag.
self.tag = transaction.tag or self.tag

# If the message is too long to fit in a single transactions,
# it must be split up into multiple transactions so that it will
# fit.
Expand Down Expand Up @@ -835,7 +864,7 @@ def add_inputs(self, inputs):
)

# Add the input as a transaction.
self.add_transaction(ProposedTransaction(
self._transactions.append(ProposedTransaction(
address = addy,
tag = self.tag,

Expand All @@ -848,7 +877,7 @@ def add_inputs(self, inputs):
# transaction length limit.
# Subtract 1 to account for the transaction we just added.
for _ in range(AddressGenerator.DIGEST_ITERATIONS - 1):
self.add_transaction(ProposedTransaction(
self._transactions.append(ProposedTransaction(
address = addy,
tag = self.tag,

Expand All @@ -867,16 +896,7 @@ def send_unspent_inputs_to(self, address):
if self.hash:
raise RuntimeError('Bundle is already finalized.')

# Negative balance means that there are unspent inputs.
# See :py:meth:`balance` for more info.
unspent_inputs = -self.balance

if unspent_inputs > 0:
self.add_transaction(ProposedTransaction(
address = address,
value = unspent_inputs,
tag = self.tag,
))
self.change_address = address

def finalize(self):
# type: () -> None
Expand All @@ -886,21 +906,34 @@ def finalize(self):
if self.hash:
raise RuntimeError('Bundle is already finalized.')

if not self:
raise ValueError('Bundle has no transactions.')

# Quick validation.
balance = self.balance
if balance > 0:

if balance < 0:
if self.change_address:
self.add_transaction(ProposedTransaction(
address = self.change_address,
value = -balance,
tag = self.tag,
))
else:
raise ValueError(
'Bundle has unspent inputs (balance: {balance}); '
'use ``send_unspent_inputs_to`` to create '
'change transaction.'.format(
balance = balance,
),
)
elif balance > 0:
raise ValueError(
'Inputs are insufficient to cover bundle spend '
'(balance: {balance}).'.format(
balance = balance,
),
)
elif balance < 0:
raise ValueError(
'Bundle has unspent inputs (balance: {balance}).'.format(
balance = balance,
),
)

# Generate bundle hash.
sponge = Curl()
Expand Down
Loading