Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft 02 impl #67

Merged
merged 7 commits into from
Jun 22, 2017
Merged

Draft 02 impl #67

merged 7 commits into from
Jun 22, 2017

Conversation

sbellem
Copy link
Contributor

@sbellem sbellem commented Jun 20, 2017

Note to reviewers

Since this is a relatively big PR, just ask me if you have questions, chances are that it'll be faster.

I have tried to minimize any changes except those needed for the upgrade.

Moreover the changes are more or less a translation of the JS implementation.

Known things left to do

@sbellem sbellem requested review from r-marques and diminator June 20, 2017 09:18
@codecov-io
Copy link

codecov-io commented Jun 20, 2017

Codecov Report

Merging #67 into master will increase coverage by 3.68%.
The diff coverage is 87.86%.

@@            Coverage Diff             @@
##           master      #67      +/-   ##
==========================================
+ Coverage    85.5%   89.19%   +3.68%     
==========================================
  Files          15       15              
  Lines         766      824      +58     
==========================================
+ Hits          655      735      +80     
+ Misses        111       89      -22

fulfillment = Fulfillment.from_uri(serialized_fulfillment)
condition_uri = fulfillment.condition_uri
if condition_uri != serialized_condition:
raise Exception(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should define an exception for this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yes


def fulfillment_to_condition(serialized_fulfillment):
fulfillment = Fulfillment.from_uri(serialized_fulfillment)
return fulfillment.condition_uri()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

condition_uri is a property

@@ -74,63 +94,98 @@ def from_uri(serialized_condition):
Returns:
Condition: Resulting object
"""
# TODO consider removing.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Don't see the reason for it. The check to see if its a string should be enough

if not re.match(CONDITION_REGEX_STRICT, serialized_condition):
raise ValueError('Invalid condition format')

if pieces[0] != CONDITION_URI_SCHEME:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need for this check here? Wouldn't the regex_match also catch this error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think the regex would be enough. I kept it there as this check was already there.

I guess it is useful to give a more explicit error message as to what is wrong.

def max_fulfillment_length(self, value):
# TODO update docstrings
@cost.setter
def cost(self, value):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to be consistent we should to cost the same we did to hash. Check that the value has the correct type on the setter and and raise a ValueError on the property if the value is not set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: #69

# Bitmask can have at most 32 bits with current implementation
if self.bitmask > Condition.MAX_SAFE_BITMASK:
raise ValueError('Bitmask too large to be safely represented')
# Subtypes can have at most 32 bits with current implementation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this check. We are not dealing with bits here. We are only checking the number of subtypes.
I think the next check for supported subtypes should be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it is somewhat clumsy. This was done quickly, as the JS implementation had yet to be updated there. See interledgerjs/five-bells-condition#67

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: #70

condition_type = TypeRegistry.find_by_type_id(self.type_id)
condition_class = condition_type['class']
asn1_dict = {
'type': condition_class.TYPE_ASN1_CONDITION,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just use condition_type['asn1_condition'] instead of declaring condition_class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, yes, we need it for the TYPE_CATEGORY though ...

"""
# TODO Use more precise Exception class
if len(value) != 32:
raise Exception(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to raise a ValueError here instead of Exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing it now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 91d6852

# This is a stricter version based on limitations of the current implementation.
# Specifically, we can't handle bitmasks greater than 32 bits.
CONDITION_REGEX_STRICT = \
r'^cc:([1-9a-f][0-9a-f]{0,3}|0):[1-9a-f][0-9a-f]{0,7}:[a-zA-Z0-9_-]{0,86}:([1-9][0-9]{0,17}|0)$'
CONDITION_REGEX_STRICT = CONDITION_REGEX
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to have both CONDITION_REGEX_STRICT and CONDITION_REGEX since they are now the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know 😄

we can clean that yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: #71

raise ParsingError(
'Invalid condition format: "cost" parameter or value missing.')

if not re.match(INTEGER_REGEX, cost):
Copy link
Contributor

@r-marques r-marques Jun 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same as int(cost)?
It would eliminate the need for INTEGER_REGEX

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, sure

issue: #72

condition.cost = value['cost']
condition._subtypes = set()
if registered_type['class'].TYPE_CATEGORY == 'compound':
subtypes = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of itertools. But I feel that this could be in a separate method. Something like subtypes_from_bitstring and subtypes_to_bitstring for to_ans1_dict

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the asn1 lib provide something to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, good point!

# The asn1 json payload that ILP uses in the JS implementation differs from
# the dictionary payload generated by pyasn1 -- hence the question:
# Is there a standard JSON representation of a condition?
def to_asn1_json(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could reuse to_asn1_dict. The value is the payload produced by to_asn1_dict

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 0359c52

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: broke the thing and fixed it now

@@ -0,0 +1 @@
"""ASN.1 definition for an RSA public key object."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, removed it. The JS implementation had one.

@@ -23,13 +26,49 @@ def get_class_from_type_id(type_id):
"""
# Determine type of condition
if type_id > MAX_SAFE_INTEGER_JS:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check here seems unnecessary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't know, this came from the JS implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: #74



class BaseSha256Fulfillment(Fulfillment):
class BaseSha256(Fulfillment):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this class? It seems that this method could just be added to the Fulfillment class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, good point. I think the idea is that the Fulfillment is "SHA" agnostic. This way, one can imagine in the near future a new suite of types based of SHA-512 for instance, that would still use the class Fulfillment as a parent class.

As a side note: These things could be achieved via mixins probably.

self._signature = signature

# TODO check type or use static typing (mypy)
def _validate_public_key(self, public_key):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check isinstance(public_key, bytes) the same way we do with the signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@sbellem sbellem mentioned this pull request Jun 21, 2017
raise Exception(
'Public key must be {} bytes, was: {}'.format(
self.PUBLIC_KEY_LENGTH, len(public_key)))
# TODO More validation? Ask ILP folks.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The underlying crypto library should already do extra checks. In fact this could just call VerifyKey and see if an exception is thrown.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


def parse_asn1_dict_payload(self, data):
self.public_key = data['publicKey']
self.signature = data['signature']

def validate(self, message=None, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a reason for message to be an optional argument. verify should fail if message is None. Also I don't see a reason for **kwargs

def bitmask(self):
return self.FEATURE_BITMASK

#def write_hash_payload(self, hasher):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove all this commented code?


@property
def bitmask(self):
return self.FEATURE_BITMASK
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FEATURE_BITMASK is not declared anywhere.

@prefix.setter
def prefix(self, prefix):
if not isinstance(prefix, bytes):
raise TypeError('Prefix must be a Buffer, was: {}'.format(prefix))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must be a byte string

"""
self._prefix = b''
self._subcondition = None
self.max_message_length = 16384
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be self._max_message_length?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 16384?

TYPE_ASN1_FULFILLMENT = 'prefixSha256Fulfillment'
TYPE_CATEGORY = 'compound'

CONSTANT_BASE_COST = 16384
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these two constants used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, they are not used right now

verify this fulfillment to begin with.

"""
return {t for t in chain(self.subcondition.subtypes,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work with arbitrarily deep subconditions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I am not wrong, the subcondition.subtypes should list all subtypes, regardless of the nesting level

cost = (len(self.prefix) +
self.max_message_length + subcondition_cost + 1024)
return cost
#(len(self.prefix) +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

@r-marques
Copy link
Contributor

Some next steps that can be done after this as been merged.

I don't think we should expose methods like to/from asn1_dict/asn1_json since they are on the spec and are specific to different asn1 implementations.
We should only expose binary, uri and json. Ideally we would have a json schema that is part of the cryptoconditions spec.

We should cleanup conftest.py. There are a lot of constants and fixtures that are no longer being used.

The crypto module does not seem to be used by cryptoconditions anymore. BigchainDB probably still uses it. Ideally we would move the code we need into BigchainDB and remove the crypto module from cryptoconditions.

@sbellem sbellem force-pushed the draft-02-impl branch 2 times, most recently from 9e61063 to 858f93a Compare June 22, 2017 08:53
@sbellem sbellem force-pushed the draft-02-impl branch 2 times, most recently from c9038d4 to 846b0cd Compare June 22, 2017 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants