Skip to content

Commit

Permalink
add contract is_complete and sign api calls
Browse files Browse the repository at this point in the history
  • Loading branch information
F483 committed Feb 8, 2016
1 parent 9fd34f0 commit a2bcfe9
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 5 deletions.
25 changes: 23 additions & 2 deletions storjlib/api.py
Expand Up @@ -21,14 +21,27 @@ def contract_validate(self, contract):

@apigen.command()
def contract_sign(self, contract, key):
raise NotImplementedError()
# TODO validate input
return storjlib.contract.sign(contract, key)

@apigen.command()
def contract_is_complete(self, contract):
raise NotImplementedError()
# TODO validate input
return storjlib.contract.is_complete(contract)

@apigen.command()
def audit_validate(self, proof, root, challengenum, leaves):
""" Validate an audit proof is for a given root and challange.
Args:
proof: The proof to be validated.
root: The merkle root the proof must be for.
challengenum: The leaf the proof must be for.
leaves: All audit leaves (to ensure proof is not for other leaves).
Retruns:
True if the proof is correct.
"""

# validate input
assert(isinstance(proof, list))
Expand All @@ -51,6 +64,14 @@ def audit_perform(self, shardid, leaves, challenge):
def audit_prepare(self, shardid, challenges):
raise NotImplementedError()

@apigen.command()
def store_import(self, paths):
raise NotImplementedError()

@apigen.command()
def store_export(self, shardid, path):
raise NotImplementedError()

@apigen.command()
def store_add(self, shard_path):
# TODO validate input
Expand Down
13 changes: 12 additions & 1 deletion storjlib/audit.py
Expand Up @@ -3,6 +3,17 @@


def validate(proof, root, challengenum, leaves):
""" Validate an audit proof is for a given root and challange.
Args:
proof: The proof to be validated.
root: The merkle root the proof must be for.
challengenum: The leaf the proof must be for.
leaves: All audit leaves (to ensure proof is not for other leaves).
Retruns:
True if the proof is correct.
"""
width = util.next_power_of_two(len(leaves))
depth = util.perfect_binary_tree_depth(width)
leaf = leaves[challengenum]
Expand Down Expand Up @@ -41,7 +52,7 @@ def trim(proof, index):
proof_next.append(util.hash_hex(branch[0] + branch[1]))
else:
proof_next.append(branch)
return trim(proof_next, index / 2)
return trim(proof_next, index / 2) # FIXME check it floors division!!!


def collapse(proof, leaf, depth):
Expand Down
98 changes: 98 additions & 0 deletions storjlib/contract/__init__.py
@@ -1,4 +1,8 @@
import json
import jsonschema
import binascii
import btctxstore
from storjlib import util
from . schema import CONTRACT_SCHEMA


Expand All @@ -8,3 +12,97 @@ def validate(contract):
return True
except jsonschema.exceptions.ValidationError:
return False


def _is_filled(contract, exclude=None):
if exclude is not None:
assert(exclude in contract.keys())
for key, value in contract.items():
if exclude is not None and key in exclude:
assert(contract[key] is None) # excluded values must be None
continue
if value is None:
return False
return True


def _generate_signature_message(contract):
message_fields = contract.copy()
del message_fields["farmer_signature"]
del message_fields["renter_signature"]
return json.dumps(message_fields, sort_keys=True)


def sign(contract, key):

_btcapi = btctxstore.BtcTxStore()

# must be a valid contract
validate(contract)

# everything but signatures must be given
exclude = ["farmer_signature", "renter_signature"]
assert(_is_filled(contract, exclude=exclude))

# key must be wif or hwif
wif = None
if _btcapi.validate_wallet(key): # hwif given
wif = _btcapi.get_key(key)
elif _btcapi.validate_key(key): # wif given
wif = key
assert(wif is not None) # key is not wif or hwif
btc_address = _btcapi.get_address(wif)

# signing key must be from farmer or renter
hexnodeid = binascii.hexlify(util.address_to_nodeid(btc_address))
assert(hexnodeid in [contract["farmer_id"], contract["renter_id"]])

# sign contract
message = _generate_signature_message(contract)
signature = _btcapi.sign_unicode(wif, message)

# add signature to contract
sigfield = None
if contract["renter_id"] == hexnodeid:
sigfield = "renter_signature"
elif contract["farmer_id"] == hexnodeid:
sigfield = "farmer_signature"
assert(sigfield is not None) # impossable state
contract[sigfield] = signature

return contract


def is_complete(contract):

# Must be valid
validate(contract)

# All fields must be filled
if not _is_filled(contract):
return False

# TODO check other things that need to be logically correct

# check duration
duration = contract["store_duration"]
begin = contract["store_begin"]
end = contract["store_end"]
if end - begin != duration:
return False

# check signatures
_btcapi = btctxstore.BtcTxStore()
message = _generate_signature_message(contract)
renter_btc_address = util.hexnodeid_to_address(contract["renter_id"])
farmer_btc_address = util.hexnodeid_to_address(contract["farmer_id"])
if not _btcapi.verify_signature_unicode(renter_btc_address,
contract["renter_signature"],
message):
return False # invalid renter signature
if not _btcapi.verify_signature_unicode(farmer_btc_address,
contract["farmer_signature"],
message):
return False # invalid renter signature

return True
14 changes: 12 additions & 2 deletions storjlib/util.py
Expand Up @@ -42,9 +42,19 @@ def address_to_nodeid(address):
return a2b_hashed_base58(address)[1:]


def nodeid_to_address(node_id):
def address_to_hexnodeid(address):
"""Convert a bitcoin address to a hex node id."""
return binascii.hexlify(address_to_nodeid(address))


def nodeid_to_address(nodeid):
"""Convert a node id to a bitcoin address."""
return b2a_hashed_base58(b'\0' + node_id)
return b2a_hashed_base58(b'\0' + nodeid)


def hexnodeid_to_address(hexnodeid):
"""Convert a hex node id to a bitcoin address."""
return nodeid_to_address(binascii.unhexlify(hexnodeid))


def chunks(items, size):
Expand Down

0 comments on commit a2bcfe9

Please sign in to comment.