"""Keyed hashing, key derivation, Merkle roots and Ed25519. Non-normative; see spec/05-hashes.md and spec/08-fidelity.md. HKDF and Ed25519 come from the `cryptography` package. """ import hashlib from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey from cryptography.hazmat.primitives.kdf.hkdf import HKDF from . import jcs def hkdf(ikm, salt, info, length=32): """HKDF-SHA-256, RFC 5869.""" return HKDF(algorithm=hashes.SHA256(), length=length, salt=salt, info=info).derive(ikm) def team_key(secret): return hkdf(secret, b"scienting-team-key/1", b"scienting-hash/1") def key_id(key): return hashlib.sha256(b"scienting-key-id/1" + key).hexdigest()[:16] def page_hash(page_fp_hex): return hashlib.sha256(b"scienting-page-hash/1" + bytes.fromhex(page_fp_hex)).hexdigest() def page_key(page_fps_hex, context): ikm = b"".join(bytes.fromhex(h) for h in page_fps_hex) return hkdf(ikm, b"scienting-page-key/1", context.encode("utf-8")) # --- Merkle trees (RFC 6962 hashing) ------------------------------------------ def _leaf(data): return hashlib.sha256(b"\x00" + data).digest() def _node(left, right): return hashlib.sha256(b"\x01" + left + right).digest() def _mth(leaves): n = len(leaves) if n == 1: return leaves[0] k = 1 while k * 2 < n: k *= 2 return _node(_mth(leaves[:k]), _mth(leaves[k:])) def merkle_root(items): """Root over receipt items: each item is a list such as ["node", id, hash].""" if not items: raise ValueError("a Merkle root needs at least one item") return _mth([_leaf(jcs.dumpb(list(it))) for it in items]).hex() def receipt_items(receipt): items = [["node", nid, h] for nid, h in receipt.get("nodes", [])] items += [list(r) for r in receipt.get("records", [])] return items # --- Ed25519 (RFC 8032) ------------------------------------------------------- def ed25519_public(secret): """The 32-byte public key of a 32-byte Ed25519 secret.""" return Ed25519PrivateKey.from_private_bytes(secret).public_key().public_bytes_raw() def ed25519_sign(secret, msg): return Ed25519PrivateKey.from_private_bytes(secret).sign(msg) def ed25519_verify(public, msg, signature): """True when `signature` is a valid Ed25519 signature of `msg` under `public`; never raises.""" if len(public) != 32 or len(signature) != 64: return False try: Ed25519PublicKey.from_public_bytes(public).verify(signature, msg) except (InvalidSignature, ValueError): return False return True