hypercore-crypto/hypercore_crypto/crypto.py

158 lines
3.8 KiB
Python
Raw Normal View History

2019-08-07 06:31:30 +00:00
"""Cryptography primitives for Hypercore."""
2019-10-05 18:28:55 +00:00
from hashlib import blake2b
2019-10-05 19:22:01 +00:00
from typing import Optional, Sequence, Tuple
2019-10-05 09:34:57 +00:00
2019-10-05 18:28:55 +00:00
from merkle_tree_stream import MerkleTreeNode
from pysodium import (
2019-10-05 18:28:55 +00:00
crypto_generichash,
crypto_generichash_BYTES,
crypto_sign_detached,
2019-08-07 06:31:30 +00:00
crypto_sign_keypair,
crypto_sign_seed_keypair,
crypto_sign_SEEDBYTES,
crypto_sign_verify_detached,
2019-10-05 18:28:55 +00:00
randombytes,
2019-08-07 06:31:30 +00:00
)
2019-10-06 14:19:18 +00:00
__all__ = [
"key_pair",
"sign",
"verify",
"data",
"leaf",
"parent",
"tree",
"random_bytes",
"discovery_key",
2019-10-06 14:19:18 +00:00
]
2019-08-07 06:31:30 +00:00
# https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
2019-10-05 19:22:01 +00:00
LEAF_TYPE = bytes([0])
PARENT_TYPE = bytes([1])
ROOT_TYPE = bytes([2])
HYPERCORE = bytes("hypercore", encoding="utf-8")
2019-08-07 06:31:30 +00:00
2019-10-05 09:34:57 +00:00
def key_pair(seed: Optional[bytes] = None) -> Tuple[bytes, bytes]:
"""A new public key and secret key pair.
2019-08-07 06:31:30 +00:00
:param seed: Seed value. Must be at least 32 characters in length
2019-10-05 09:34:57 +00:00
"""
2019-08-07 06:31:30 +00:00
if seed:
if len(seed) < crypto_sign_SEEDBYTES:
message = "'seed' argument must be of length > {}"
raise ValueError(message.format(crypto_sign_SEEDBYTES))
2019-10-05 09:34:57 +00:00
return crypto_sign_seed_keypair(seed)
2019-10-05 18:28:55 +00:00
2019-10-05 09:34:57 +00:00
return crypto_sign_keypair()
2019-08-07 06:31:30 +00:00
2019-10-05 09:34:57 +00:00
def sign(message: bytes, secret_key: bytes) -> bytes:
"""A message signature.
:param message: The message to be signed
:param secret_key: The secret key to use during signing
"""
return crypto_sign_detached(message, secret_key)
2019-08-07 06:31:30 +00:00
def verify(message: bytes, signature: bytes, public_key: bytes) -> bool:
"""Verify an unsigned message with accompanying signature.
:param message: The unsigned message to verify
:param signature: The signature to be verified
:param public_key: The public key to use during verifying
"""
2019-10-05 09:34:57 +00:00
try:
crypto_sign_verify_detached(signature, message, public_key)
except ValueError:
2019-10-05 09:34:57 +00:00
return False
2019-10-05 18:28:55 +00:00
2019-10-05 09:34:57 +00:00
return True
2019-08-07 06:31:30 +00:00
2019-10-05 18:28:55 +00:00
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])
2019-10-05 19:22:01 +00:00
def leaf(leaf: MerkleTreeNode) -> bytes:
2019-10-05 18:28:55 +00:00
"""The hashed digest of the leaf.
:param leaf: The leaf data to be hashed
"""
return data(leaf.data)
2019-08-07 06:31:30 +00:00
2019-10-05 19:22:01 +00:00
def parent(child: MerkleTreeNode, parent: MerkleTreeNode) -> bytes:
2019-10-05 18:28:55 +00:00
if child.index > parent.index:
raise ValueError("Child index is greater than parent?")
2019-08-07 06:31:30 +00:00
2019-10-05 18:28:55 +00:00
values = [
PARENT_TYPE,
_to_unsigned_64_int(child.size + parent.size),
child.hash,
parent.hash,
]
2019-08-07 06:31:30 +00:00
2019-10-05 18:28:55 +00:00
return _blake2bify(values)
2019-08-07 06:31:30 +00:00
2019-10-05 19:22:01 +00:00
def tree(roots: Sequence[MerkleTreeNode]) -> bytes:
2019-10-05 18:28:55 +00:00
"""Hashed tree roots.
:param roots: A list of root nodes
"""
to_be_hashed = []
to_be_hashed.append(ROOT_TYPE)
2019-08-07 06:31:30 +00:00
2019-10-05 18:28:55 +00:00
for root in roots:
to_be_hashed.append(root.hash)
to_be_hashed.append(root.index)
to_be_hashed.append(root.size)
2019-08-07 06:31:30 +00:00
2019-10-05 18:28:55 +00:00
return _blake2bify(to_be_hashed)
2019-08-07 06:31:30 +00:00
2019-10-05 18:28:55 +00:00
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(public_key: bytes) -> bytes:
"""The discovery key for a tree.
:param public_key: The public key for hashing
"""
return crypto_generichash(HYPERCORE, k=public_key)
2019-10-05 18:28:55 +00:00
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)
2019-10-05 18:28:55 +00:00
2019-10-05 19:22:01 +00:00
def _blake2bify(data: Sequence[bytes]) -> bytes:
2019-10-05 18:28:55 +00:00
"""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)
2019-08-07 06:31:30 +00:00
2019-10-05 18:28:55 +00:00
return hash_func.digest()