From e1c0b8006cd224d73b7d6635d99373c5c1cf3ef4 Mon Sep 17 00:00:00 2001 From: rsoultanaev Date: Thu, 1 Mar 2018 07:50:03 +0000 Subject: [PATCH 1/2] Add conformance test --- .../conformance_test/conformance_client.py | 33 +++++++ contrib/conformance_test/conformance_test.py | 96 +++++++++++++++++++ .../conformance_test_usage.md | 40 ++++++++ 3 files changed, 169 insertions(+) create mode 100644 contrib/conformance_test/conformance_client.py create mode 100644 contrib/conformance_test/conformance_test.py create mode 100644 contrib/conformance_test/conformance_test_usage.md diff --git a/contrib/conformance_test/conformance_client.py b/contrib/conformance_test/conformance_client.py new file mode 100644 index 0000000..d11e134 --- /dev/null +++ b/contrib/conformance_test/conformance_client.py @@ -0,0 +1,33 @@ +from sphinxmix.SphinxClient import * +from sphinxmix.SphinxParams import SphinxParams +from sphinxmix.SphinxNode import sphinx_process + +from petlib.bn import Bn +from petlib.ec import EcPt + +from base64 import b64decode + +import sys + +sphinx_params = SphinxParams() +args = sys.argv[1:] + +dest = b64decode(args[0]) +message = b64decode(args[1]) + +nodes_routing = [] +node_keys = [] +for node_key_pair in args[2:]: + node_str, key_str = node_key_pair.split(':') + + node = Nenc(int(node_str)) + key = EcPt.from_binary(b64decode(key_str), sphinx_params.group.G) + + nodes_routing.append(node) + node_keys.append(key) + +header, delta = create_forward_message(sphinx_params, nodes_routing, node_keys, dest, message) +bin_message = pack_message(sphinx_params, (header, delta)) + +sys.stdout.buffer.write(bin_message) + diff --git a/contrib/conformance_test/conformance_test.py b/contrib/conformance_test/conformance_test.py new file mode 100644 index 0000000..e0ac770 --- /dev/null +++ b/contrib/conformance_test/conformance_test.py @@ -0,0 +1,96 @@ +from sphinxmix.SphinxClient import * +from sphinxmix.SphinxParams import SphinxParams +from sphinxmix.SphinxNode import sphinx_process + +from petlib.bn import Bn + +from base64 import b64encode + +import sys +import subprocess + +pki_tuple = namedtuple('pki_tuple', ['id', 'private_key', 'public_key']) + +def run_client_under_test(client_command, dest, message, use_nodes, node_keys): + dest_encoded = b64encode(dest).decode() + message_encoded = b64encode(message).decode() + + node_key_pairs = [] + for i in range(len(use_nodes)): + pair = str(use_nodes[i]) + ':' + b64encode(node_keys[i].export()).decode() + node_key_pairs.append(pair) + + run_command = [] + for command in client_command.split(' '): + run_command.append(command) + + run_command.append(dest_encoded) + run_command.append(message_encoded) + + for node_key_pair in node_key_pairs: + run_command.append(node_key_pair) + + return subprocess.run(run_command, stdout=subprocess.PIPE).stdout + + +def initialise_pki(sphinx_params, num_mix_nodes): + pki = {} + + for i in range(num_mix_nodes): + node_id = i + private_key = sphinx_params.group.gensecret() + public_key = sphinx_params.group.expon(sphinx_params.group.g, [private_key]) + pki[node_id] = pki_tuple(node_id, private_key, public_key) + + return pki + + +def route_message_through_network(sphinx_params, pki, current_node_id, bin_message): + param_dict = { (sphinx_params.max_len, sphinx_params.m): sphinx_params } + _, (header, delta) = unpack_message(param_dict, bin_message) + + current_private_key = pki[current_node_id].private_key + + while True: + ret = sphinx_process(sphinx_params, current_private_key, header, delta) + (tag, B, (header, delta), mac_key) = ret + routing = PFdecode(sphinx_params, B) + + if routing[0] == Relay_flag: + current_node_id = routing[1] + current_private_key = pki[current_node_id].private_key + elif routing[0] == Dest_flag: + return receive_forward(sphinx_params, mac_key, delta) + else: + return None + + +def test_create_forward_message_creation(client_command, num_mix_nodes=10, num_path_nodes=5): + sphinx_params = SphinxParams() + + pki = initialise_pki(sphinx_params, num_mix_nodes) + + use_nodes = rand_subset(pki.keys(), num_path_nodes) + nodes_routing = list(map(Nenc, use_nodes)) + node_keys = [pki[n].public_key for n in use_nodes] + + dest = b'bob' + message = b'this is a test' + + bin_message = run_client_under_test(client_command, dest, message, use_nodes, node_keys) + + routing_result = route_message_through_network(sphinx_params, pki, use_nodes[0], bin_message) + if routing_result: + routed_dest, routed_message = routing_result + + if routed_dest == dest and routed_message == message: + print('Success') + else: + print('Failure') + else: + print('Failure') + + +if __name__ == '__main__': + test_create_forward_message_creation(sys.argv[1]) + diff --git a/contrib/conformance_test/conformance_test_usage.md b/contrib/conformance_test/conformance_test_usage.md new file mode 100644 index 0000000..3a4f67c --- /dev/null +++ b/contrib/conformance_test/conformance_test_usage.md @@ -0,0 +1,40 @@ +# Forward Sphinx message conformance test from X to Python + +This is a conformance test that allows testing whether a forward message created using the given Sphinx client is conformant with the Python implementation of Sphinx. That is, if the given client were to be used to create a forward Sphinx message, and this message was forwarded to a Sphinx network that runs based off of the Python implementation, then it would be routed and received correctly. + +## Usage of conformance test + +Provided that `client_run_command` represents the command that runs the client under test (which works as specified below), the conformance test is ran as follows: +``` +python3 conformance_test.py 'client_run_command' +``` +For example, the provided `conformance_client.py` would be tested as follows: +``` +python3 conformance_test.py 'python3 conformance_client.py' +``` +The test will generate parameters for the client (as specified below), execute it and capture the standard output. It will then simulate routing the resulting message through a Sphinx network using the Python implementation, and checks if it gets routed correctly. `Success` will be printed if the test is successful, `Failure` otherwise. + +## Specification for client under test + +To perform the conformance test, the client under test must be implemented according to the specification described below. An example "conformance client" implemented in Python can be seen in the `conformance_client.py` file. + +The client under test must be runnable from the command line, and accept command line arguments in the following format: +``` +client_run_command [destination] [message] [mix_node_1_id]:[mix_node_1_public_key] [mix_node_2_id]:[mix_node_2_public_key] ... +``` +For example, like this: +``` +python3 conformance_client.py Ym9i dGhpcyBpcyBhIHRlc3Q= 4:AsNPySfFEjwKay1+w1PMeuURgZ9aLJjSbHdOaeo= 9:AntpLX/rveimvKCA1piwMTV4yKqH9b7EyXxROTw= +``` +Specifics: +* `client_run_command` is a command that is used to run the client, that can contain spaces. For example: + * `./client` + * `python3 client.py` + * `java -jar client.jar` +* `[destination]` is the Sphinx packet destination as a base64-encoded string +* `[message]` is the Sphinx packet message as a base64-encoded string +* `[mix_node_n_id]` is a string representation of the id for the n-th Sphinx node (e.g `1`) +* `[mix_node_n_public_key]` is a base64-encoded string representation of the n-th Sphinx node's public key +* The client should be able to accept a varying number of (id, public key) pairs + +The client must use the input to create a forward Sphinx message, and pack it appropriately into a binary representation. It must then write this message to standard output. From fd6866f607d6a6f2226b6344ed07e953075bc3a8 Mon Sep 17 00:00:00 2001 From: rsoultanaev Date: Thu, 1 Mar 2018 08:23:10 +0000 Subject: [PATCH 2/2] Move conformance_client.py code into a main function --- .../conformance_test/conformance_client.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/contrib/conformance_test/conformance_client.py b/contrib/conformance_test/conformance_client.py index d11e134..3d46881 100644 --- a/contrib/conformance_test/conformance_client.py +++ b/contrib/conformance_test/conformance_client.py @@ -9,25 +9,26 @@ import sys -sphinx_params = SphinxParams() -args = sys.argv[1:] +if __name__ == '__main__': + sphinx_params = SphinxParams() + args = sys.argv[1:] -dest = b64decode(args[0]) -message = b64decode(args[1]) + dest = b64decode(args[0]) + message = b64decode(args[1]) -nodes_routing = [] -node_keys = [] -for node_key_pair in args[2:]: - node_str, key_str = node_key_pair.split(':') + nodes_routing = [] + node_keys = [] + for node_key_pair in args[2:]: + node_str, key_str = node_key_pair.split(':') - node = Nenc(int(node_str)) - key = EcPt.from_binary(b64decode(key_str), sphinx_params.group.G) + node = Nenc(int(node_str)) + key = EcPt.from_binary(b64decode(key_str), sphinx_params.group.G) - nodes_routing.append(node) - node_keys.append(key) + nodes_routing.append(node) + node_keys.append(key) -header, delta = create_forward_message(sphinx_params, nodes_routing, node_keys, dest, message) -bin_message = pack_message(sphinx_params, (header, delta)) + header, delta = create_forward_message(sphinx_params, nodes_routing, node_keys, dest, message) + bin_message = pack_message(sphinx_params, (header, delta)) -sys.stdout.buffer.write(bin_message) + sys.stdout.buffer.write(bin_message)