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

[WIP] Support for Flash Channels #152

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
141 changes: 141 additions & 0 deletions examples/flash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
from iota import Address
from iota.crypto.types import Seed
from iota.flash.api import FlashIota
from iota.flash.types import FlashUser, FlashState

ONE_SEED = Seed(b'USERONEUSERONEUSERONEUSERONEUSERONEUSERONEUSERONEUSERONEUSERONEUSERONEUSERONEUSER')
ONE_SETTLEMENT = Address(b'USERONE9ADDRESS9USERONE9ADDRESS9USERONE9ADDRESS9USERONE9ADDRESS9USERONE9ADDRESS9U')
TWO_SEED = Seed(b'USERTWOUSERTWOUSERTWOUSERTWOUSERTWOUSERTWOUSERTWOUSERTWOUSERTWOUSERTWOUSERTWOUSER')
TWO_SETTLEMENT = Address(b'USERTWO9ADDRESS9USERTWO9ADDRESS9USERTWO9ADDRESS9USERTWO9ADDRESS9USERTWO9ADDRESS9U')

# General channel configuration
SECURITY = 2 # security level
SIGNERS_COUNT = 2 # number of parties taking signing part in the channel
TREE_DEPTH = 4 # Flash tree depth
CHANNEL_BALANCE = 2000 # total channel Balance
DEPOSITS = [1000, 1000] # users deposits

############################################
# (0) Initialize Flash objects
############################################

print('(0) Initializing Flash objects')

# user one
iota_one = FlashIota(adapter='http://localhost:14265', seed=ONE_SEED)
flash_one = FlashState(signers_count=SIGNERS_COUNT,
balance=CHANNEL_BALANCE,
deposit=list(DEPOSITS))
user_one = FlashUser(user_index=0,
seed=ONE_SEED,
index=0,
security=SECURITY,
depth=TREE_DEPTH,
flash=flash_one)

# user two
flash_two = FlashState(signers_count=SIGNERS_COUNT,
balance=CHANNEL_BALANCE,
deposit=list(DEPOSITS))
user_two = FlashUser(user_index=1,
seed=TWO_SEED,
index=0,
security=SECURITY,
depth=TREE_DEPTH,
flash=flash_two)
iota_two = FlashIota(adapter='http://localhost:14265', seed=TWO_SEED)
############################################
# (1) Generate Digests
############################################

print('(1) Generating digests for each user')

for _ in range(TREE_DEPTH + 1):
digest = iota_one.get_digests(index=user_one.index, security_level=user_one.security)['digests']
user_one.index += 1
user_one.partial_digests.append(digest[0])

for _ in range(TREE_DEPTH + 1):
digest = iota_two.get_digests(index=user_two.index, security_level=user_two.security)['digests']
user_two.index += 1
user_two.partial_digests.append(digest[0])

############################################
# (2) Create Multisignature Addresses
############################################

print('(2) Creating multisignature addresses')

all_digests = list(zip(user_one.partial_digests, user_two.partial_digests))
one_multisigs = [iota_one.compose_flash_address(digests) for digests in all_digests]
two_multisigs = [iota_two.compose_flash_address(digests) for digests in all_digests]

############################################
# (3) Organize Addresses for use
############################################

print('(3) Organizing addresses for use')

# Set remainder address (Same on both users)
flash_one.remainder_address = one_multisigs.pop(0)
flash_two.remainder_address = two_multisigs.pop(0)

# Nest trees
for i in range(1, len(one_multisigs)):
one_multisigs[i - 1]['children'].append(one_multisigs[i])
for i in range(1, len(two_multisigs)):
two_multisigs[i - 1]['children'].append(two_multisigs[i])

# Set deposit address
flash_one.deposit_address = one_multisigs[0]
flash_two.deposit_address = two_multisigs[0]

# Set root of tree
flash_one.root = one_multisigs[0]
flash_two.root = two_multisigs[0]

# Set settlement addresses (Usually sent over when the digests are.)
settlement_addresses = [ONE_SETTLEMENT, TWO_SETTLEMENT]
flash_one.settlement_addresses = settlement_addresses
flash_two.settlement_addresses = settlement_addresses

# Set digest/key index
user_one.index = len(user_one.partial_digests)
user_two.index = len(user_two.partial_digests)

############################################
# (4) Compose Transaction from user one
############################################
print('(4) Compose transactions. Sending 200 tokens to', TWO_SETTLEMENT)

transfers = [{
'value': 200,
'address': TWO_SETTLEMENT
}]
bundles = iota_one.create_flash_transaction(user=user_one, transactions=transfers)

# ToDO

############################################
# (5) Sign Bundles
############################################

print('(5) Signing bundles')

# ToDO

############################################
# (6) Apply Signed Bundles
############################################

print('(6) Applying signed bundles')

# ToDO

############################################
# (7) Close Channel
############################################

print('(7) Closing channel')

# ToDO
1 change: 1 addition & 0 deletions iota/flash/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MAX_USES = 3
76 changes: 76 additions & 0 deletions iota/flash/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from typing import Iterable

from iota.commands import discover_commands
from iota.crypto.types import Digest
from iota.flash.commands.create_transaction import CreateFlashTransactionCommand
from iota.flash.commands.multisig.compose_address_node import ComposeAddressNodeCommand
from iota.flash.types import FlashUser
from iota.multisig import MultisigIota

__all__ = [
'FlashIota',
]


class FlashIota(MultisigIota):
"""
Extends the IOTA API so that it can manage Flash channels.

**CAUTION:** Make sure you understand how Flash channel work before
attempting to use it. If you are not careful, you could easily
compromise the security of your private keys, send IOTAs to
unspendable addresses, etc.

References:
- https://github.com/iotaledger/iota.flash.js
"""

commands = discover_commands('iota.flash.commands')

def compose_flash_address(self, digests):
# type: (Iterable[Digest]) -> dict
"""
Composes a multisig address for Flash channels from a collection of digests.

:param digests:
Digests to use to create the multisig address.

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

:return:
Dict with the following items::

{
'address': dict,
The generated multisig address object.
}
"""
return ComposeAddressNodeCommand(self.adapter)(
digests = digests,
)

def create_flash_transaction(self, user, transactions, close=False):
# type: (FlashUser, Iterable[dict], bool) -> Iterable[dict]
"""

:param user: Flash object of user storing relevant metadata of the channel
:param transactions: list of transaction, which should be executed
:param close: Flag indicating a closing of the channel
:return:
Dict with the following items::
{
'bundles': List[Bundles],,
List of generated bundles.
}
"""
return CreateFlashTransactionCommand(self.adapter)(
user=user,
transactions=transactions,
close=close
)




Empty file added iota/flash/commands/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions iota/flash/commands/create_transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# coding=utf-8
from __future__ import absolute_import, division, print_function, \
unicode_literals

import filters as f
from collections import Iterable

from iota import Address, Bundle
from iota.commands import FilterCommand, RequestFilter, ResponseFilter
from iota.filters import Trytes
from iota.flash.commands.multisig import update_leaf_to_root
from iota.flash.types import FlashUser


class CreateFlashTransactionCommand(FilterCommand):
"""
Helper for creating a transaction within a Flash channel
"""
command = 'createFlashTransaction'

def get_request_filter(self):
return CreateFlashTransactionRequestFilter()

def get_response_filter(self):
pass
# return CreateFlashTransactionResponseFilter()

def _execute(self, request):
user = request['user'] # type: FlashUser
transactions = request['transactions'] # type: Iterable[dict]
close = request['close'] # type: bool

# From the LEAF recurse up the tree to the root
# and find how many new addresses need to be generated if any.
to_use, to_generate = update_leaf_to_root(root=user.flash.root)
if to_generate != 0:
# TODO: handle this case
pass

return []


class CreateFlashTransactionRequestFilter(RequestFilter):
def __init__(self):
super(CreateFlashTransactionRequestFilter, self).__init__({
'user': f.Required | f.Type(FlashUser),
'transactions': f.Required |
f.Array |
f.FilterRepeater(f.Required |
f.FilterMapper({
'value': f.Type(int) | f.Min(0),
'address': f.Required | Trytes(result_type=Address)
})),
'close': f.Type(bool) | f.Optional(default=False)
})


class CreateFlashTransactionResponseFilter(ResponseFilter):
def __init__(self):
super(CreateFlashTransactionResponseFilter, self).__init__({
'bundles': f.Required | f.Array | f.FilterRepeater(f.Required | Trytes(result_type=Bundle))
})
66 changes: 66 additions & 0 deletions iota/flash/commands/multisig/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import List

from iota.flash import MAX_USES


def get_last_branch(root):
# type: (dict) -> List[dict]
"""
Searches for last used branch in tree and returns list of node beginning with
root node.

:param root: root of tree
:return list of nodes in last used branch beginning with root node
"""
multisigs = []
node = root
while node:
multisigs.append(node)
node = node['children'][-1] if node['children'] else None
return multisigs


def get_minimum_branch(root):
# type: (dict) -> List[dict]
"""
Searches for minimum branch in tree, which stores transactions

:param root: root of tree
:return list of nodes storing transactions
"""
multisigs = []
node = root
while node:
multisigs.append(node)
if len(node['children']) and len(node['bundles']) == MAX_USES:
node = node['children'][-1]
else:
node = None
return multisigs


def update_leaf_to_root(root):
# type: (dict) -> (dict, int)
"""
Searches tree for node in branch that can be used and number of addresses that
needs to be generated
:param root:
:returns: tuple (multisig_address, num_to_ generate)
:rtype: (dict, int)
"""
multisigs = get_last_branch(root)

# get the first one that does not pass all the way down
for i in range(len(multisigs) - 1):
transactions = multisigs[i]['bundles']
if not any([t.value > 0 and t.address == multisigs[i + 1]['address'] for t in transactions]):
return multisigs[i], 0

# TODO: TEST this section
# get the first from the bottom that is used less than MAX_USES times
num_generate = 0
for i in reversed(range(len(multisigs))):
if len(multisigs[i]['bundles']) != MAX_USES:
break
num_generate += 1
return multisigs[i], num_generate
Loading