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

The stuff to get us off coffee. #351

Merged
merged 19 commits into from Jul 10, 2018
Merged

The stuff to get us off coffee. #351

merged 19 commits into from Jul 10, 2018

Conversation

jMyles
Copy link
Member

@jMyles jMyles commented Jul 5, 2018

  • Ursula now signs her verifying key with her ethereum key.
  • Nodes in decentralized mode verify Ursula's wallet signature.
  • Ursula includes her public address in her signed interface info.
  • All learning nodes now track other nodes by address.
  • Several new tests again plausible attacks, such as Vladimir copies an Ursula, but substitues his own Ethereum address #353

Fixes #294
Fixes #295

@jMyles jMyles changed the base branch from master to coffee July 5, 2018 01:50
@KPrasch
Copy link
Member

KPrasch commented Jul 5, 2018

Build failure due to a version of bytestring splitter?

Copy link
Contributor

@tuxxy tuxxy left a comment

Choose a reason for hiding this comment

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

We need to talk about returning Signature in blockchain power. This isn't a great idea, imho.

@@ -58,7 +58,7 @@ def rpc_store(self, sender, nodeid, key, value):
if header == constants.BYTESTRING_IS_URSULA_IFACE_INFO:
from nucypher.characters import Ursula
stranger_ursula = Ursula.from_bytes(payload,
federated_only=True) # TODO: Is federated_only the right thing here?
federated_only=self.sourceNode.federated_only) # TODO: Is federated_only the right thing here?
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 On having this discussion at some point. Good TODO.

@@ -196,8 +190,7 @@ def get_nodes_via_rest(self, address, port, node_ids):
return response

def put_treasure_map_on_node(self, node, map_id, map_payload):
running_node = _ALL_URSULAS[node.rest_interface.port]
mock_client = TestClient(running_node.rest_app)
mock_client = self.__get_mock_client_by_port(node.rest_interface.port)
Copy link
Contributor

Choose a reason for hiding this comment

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

Thoughts on making this a fixture?

- mypy_type_check:
requires:
- bundle_dependencies-36
# - mypy_type_check:
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we just allow failure here

@@ -99,7 +104,7 @@ def sign_message(self, message: bytes):
signature = self.blockchain.interface.call_backend_sign(self.account, message)
return signature

def verify_message(self, address: str, pubkey: bytes, message: bytes, signature: str):
def verify_message(self, address: str, pubkey: bytes, message: bytes, signature: Signature):
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a step in the wrong direction.

This is meant to be an interface to what the blockchain sees. Providing the eth_keys Signature object is only going to allow users to make mistakes with that.

This is meant to be an interface for exactly what a smart contract would most likely interact with. I'm not very keen on providing an object that we don't have control over.

Copy link
Member Author

@jMyles jMyles Jul 5, 2018

Choose a reason for hiding this comment

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

Providing the eth_keys Signature object is only going to allow users to make mistakes with that.

...why? Why doesn't it also allow them to do the right thing with that?

I'm not very keen on providing an object that we don't have control over.

Wait, what? Are you saying you are just wholesale against returning objects from dependency libraries?

I don't see how it makes any sense to return the hex. We never use it that way.

For my part, I don't even really see the point of verify_message on BlockchainPower - I prefer to deprecate it entirely rather than make design decisions based on it.

At the end of the day, all we care about it that we have the correct public address, and we can get that by deriving it from the public key, which we can get from the signature. This is really not the same use case as general-purpose signing. The Signature class serves us very well for our singular need.

Copy link
Contributor

Choose a reason for hiding this comment

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

Providing the raw signature allows them to use eth_keys very not misuse resistant API. Potentially they could use that to verify the signature and not the BlockchainPower and by very simple accident, would allow them to verify a signature improperly. This should not be that simple.

Wait, what? Are you saying you are just wholesale against returning objects from dependency libraries?

That's not what I said. I'm not very keen on providing an object we don't have control over in such a critical interface as this. If we wanted to make our own Signature class that is misuse resistant, then I'm all for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Essentially, I'm saying that eth_keys Signature class is too low-level to be exposed here. If we can restrict what can be done on this Signature class, then we could return that. Like we discussed in PDX, I would prefer this, but the ecosystem has yet to catch up here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I see.

Well, I think that the proper pattern here is:

  • Have address to verify
  • Have message purporting to come from address
  • Have signature against this message
  • Validate signature
  • Get public key from signature
  • Derive address from signature
  • Confirm that addresses match

That appears to be the intention of this object, and it performs this task very well. So, do you want to cast to bytes and then cast back to Signature? I guess that's fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that would be ideal (for now). In the verify_message method, at least.

@@ -418,7 +418,7 @@ def call_backend_sign(self, account: str, message: bytes) -> str:
address = to_canonical_address(account)
sig_key = provider.ethereum_tester.backend._key_lookup[address]
signed_message = sig_key.sign_msg(message)
return signed_message.to_hex()
return signed_message
Copy link
Contributor

Choose a reason for hiding this comment

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

This breaks parity with the rest of the web3 API.

So, if we're running a test, this API is called, which returns a string. In most cases, this is what we should expect when calling some node to sign something for us, not the object.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is why I hate this code and want a better signing solution from web3py here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I... don't see the problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

In one case (running tests), we return a Signature. In another, we return a string.

This is not good API design for signature, imho. They should both return an object (which is problematic), or they should both return a string.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh, I see. So w3.eth.sign returns a string?

In any case, remember this is a [WIP] PR - I haven't even looked at the else clause in this function yet.

@jMyles jMyles changed the title [WIP] The stuff to get us off coffee. The stuff to get us off coffee. Jul 10, 2018
@jMyles
Copy link
Member Author

jMyles commented Jul 10, 2018

I propose that, upon merging this, we freeze #286 for review.

Copy link
Contributor

@tuxxy tuxxy left a comment

Choose a reason for hiding this comment

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

🐧 Looks good, nice changes.

@jMyles jMyles mentioned this pull request Jul 10, 2018

def verify_message(self, address: str, pubkey: bytes, message: bytes, signature: str):
def verify_message(self, address: str, pubkey: bytes, message: bytes, signature_bytes: bytes):
Copy link
Contributor

Choose a reason for hiding this comment

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

Not that it matters much, but is it necessary to have the type indicated in the argument name as signature_bytes rather than just naming signature?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, this is a perennial wonder of mine. Since we make a Signature object in the next line, doesn't it make sense to clarify that this object is a bytes representation? I dunno.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not too strong on one or the other way though. It's not super-important here. So, I'm ok with _bytes

@@ -27,7 +25,7 @@ class NucypherTokenActor:
class ActorError(Exception):
pass

def __init__(self, checksum_address: str=None, token_agent: NucypherTokenAgent=None):
def __init__(self, checksum_address: str = None, token_agent: NucypherTokenAgent = None):
"""
:param checksum_address: If not passed, we assume this is an unknown actor

Copy link
Member

Choose a reason for hiding this comment

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

I don't understand what's the deal with self.checksum_public_address attribute below (L37). Isn't it always going to raise an AttributeError since it doesn't exist yet?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's a compromise for the case (which occurs in practice, almost without exception) where this class is mixed in with Character.

@KPrasch KPrasch merged commit 53d2104 into nucypher:coffee Jul 10, 2018
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.

None yet

5 participants