From a250b8391cc575414e6192acc6c2b95d28ee10d0 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Sat, 5 Oct 2019 20:28:55 +0200 Subject: [PATCH] Finalise the rest of the module --- hypercore_crypto/crypto.py | 94 ++++++++++++++++++++++++++++++++------ setup.cfg | 1 + test/test_crypto.py | 42 ++++++++++++++--- 3 files changed, 118 insertions(+), 19 deletions(-) diff --git a/hypercore_crypto/crypto.py b/hypercore_crypto/crypto.py index d493ed8..5c1c78d 100644 --- a/hypercore_crypto/crypto.py +++ b/hypercore_crypto/crypto.py @@ -1,13 +1,18 @@ """Cryptography primitives for Hypercore.""" -from typing import Optional, Tuple +from hashlib import blake2b +from typing import List, Optional, Tuple +from merkle_tree_stream import MerkleTreeNode from pysodium import ( + crypto_generichash, + crypto_generichash_BYTES, crypto_sign_detached, crypto_sign_keypair, crypto_sign_seed_keypair, crypto_sign_SEEDBYTES, crypto_sign_verify_detached, + randombytes, ) # https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack @@ -27,6 +32,7 @@ def key_pair(seed: Optional[bytes] = None) -> Tuple[bytes, bytes]: message = "'seed' argument must be of length > {}" raise ValueError(message.format(crypto_sign_SEEDBYTES)) return crypto_sign_seed_keypair(seed) + return crypto_sign_keypair() @@ -50,28 +56,90 @@ def verify(message: bytes, signature: bytes, public_key: bytes) -> bool: crypto_sign_verify_detached(signature, message, public_key) except ValueError: return False + return True -def data(data: bytes): - pass +def data(data: bytes) -> bytes: + """The hashed digest of data input. + + :param data: The data to be hashed + """ + return _blake2bify([LEAF_TYPE, _to_unsigned_64_int(len(data)), data]) -def leaf(): - pass +def leaf(leaf: MerkleTreeNode) -> str: + """The hashed digest of the leaf. + + :param leaf: The leaf data to be hashed + """ + return data(leaf.data) -def parent(): - pass +def parent(child: MerkleTreeNode, parent: MerkleTreeNode) -> str: + if child.index > parent.index: + raise ValueError('Child index is greater than parent?') + + values = [ + PARENT_TYPE, + _to_unsigned_64_int(child.size + parent.size), + child.hash, + parent.hash, + ] + + return _blake2bify(values) -def tree(): - pass +def tree(roots: List[MerkleTreeNode]) -> bytes: + """Hashed tree roots. + + :param roots: A list of root nodes + """ + to_be_hashed = [] + to_be_hashed.append(ROOT_TYPE) + + for root in roots: + to_be_hashed.append(root.hash) + to_be_hashed.append(root.index) + to_be_hashed.append(root.size) + + return _blake2bify(to_be_hashed) -def random_bytes(): - pass +def random_bytes(size: int) -> bytes: + """Random bytes with specified length. + + :param size The length of the random bytes + """ + return randombytes(size) -def discovery_key(): - pass +def discovery_key(public_key: bytes) -> bytes: + """The discovery key for a tree. + + :param public_key: The public key for hashing + """ + return crypto_generichash(HYPERCORE, key=public_key) + + +def _to_unsigned_64_int(num: int) -> bytes: + """Convert an integer to unsigned 64 bit bytes. + + See https://stackoverflow.com/a/45434265. + + :param num: The integer to be converted + """ + return int(num).to_bytes(8, byteorder='big', signed=False) + + +def _blake2bify(data: List[bytes]) -> bytes: + """Hashed bytes from the Blake2b hash function. + + :param data: A list of byte values to be hashed + """ + hash_func = blake2b(digest_size=crypto_generichash_BYTES) + + for _data in data: + hash_func.update(_data) + + return hash_func.digest() diff --git a/setup.cfg b/setup.cfg index 2a92700..638336c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,6 +44,7 @@ packages = find: zip_safe = False install_requires = pysodium >= 0.7.2, < 0.8 + merkle-tree-stream >= 0.0.1a1, < 1.0.0 [options.packages.find] where = . diff --git a/test/test_crypto.py b/test/test_crypto.py index dff0e24..4774d53 100644 --- a/test/test_crypto.py +++ b/test/test_crypto.py @@ -1,14 +1,15 @@ """Cryptography primitives test module.""" import pytest - -from hypercore_crypto import key_pair, sign, verify +from merkle_tree_stream import MerkleTreeNode from pysodium import crypto_sign_PUBLICKEYBYTES, crypto_sign_SECRETKEYBYTES +from hypercore_crypto import data, key_pair, parent, random_bytes, sign, verify + def test_key_pair_seed_length(): with pytest.raises(ValueError) as exception: - key_pair(b'wrong') + key_pair(b'world hello') assert 'must be of length' in str(exception) @@ -19,13 +20,42 @@ def test_key_pair_length(): def test_sign(): - message = b'mymessage' + message = b'hello world' _, secret_key = key_pair() - assert message not in sign(message, secret_key) + signature = sign(message, secret_key) + assert message not in signature + assert len(signature) == 64 def test_verify(): - message = b'mymessage' + message = b'hello world' public_key, secret_key = key_pair() signature = sign(message, secret_key) assert verify(message, signature, public_key) + + +def test_data_digest(): + assert ( + data(b'hello world').hex() + == 'ccfa4259ee7c41e411e5770973a49c5ceffb5272d6a37f2c6f2dac2190f7e2b7' + ) + + +def test_random_bytes(): + assert len(random_bytes(32)) == 32 + + +def test_parent_digest(): + _data = b'hello world' + _parent = parent( + MerkleTreeNode( + index=0, size=11, hash=data(_data), parent=None, data=None + ), + MerkleTreeNode( + index=2, size=11, hash=data(_data), parent=None, data=None + ), + ) + assert ( + _parent.hex() + == '43563406adba8b34b133fdca32d0a458c5be769615e01df30e6535ccd3c075f0' + )