Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

Commit

Permalink
Merge pull request #12 from iotaledger/develop
Browse files Browse the repository at this point in the history
1.0.0b5
  • Loading branch information
todofixthis authored Jan 11, 2017
2 parents 9828d48 + 0d274ce commit 332412d
Show file tree
Hide file tree
Showing 8 changed files with 606 additions and 103 deletions.
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

0 comments on commit 332412d

Please sign in to comment.