"""Signed operations: identity, integrity, determinism, tamper rejection.""" import copy from fablepool import ops from fablepool.keys import KeyPair from helpers import verifies, verify_ok FIXED_TS = 1700000000 def _signed(kp, payload=None, op_type="evidence", prev=None): body = { "v": 1, "type": op_type, "author": kp.public_key_hex, "ts": FIXED_TS, "prev": list(prev or []), "payload": dict(payload or {"source": "test", "body": "hello"}), } ret = ops.sign_op(body, kp) return ret if ret is not None else body def test_sign_op_adds_id_and_sig(): op = _signed(KeyPair.generate()) assert op.get("id") assert op.get("sig") def test_signed_op_verifies(): assert verifies(_signed(KeyPair.generate())) def test_tampered_payload_rejected(): op = _signed(KeyPair.generate()) bad = copy.deepcopy(op) bad["payload"]["body"] = "evil" assert not verifies(bad) def test_tampered_signature_rejected(): op = _signed(KeyPair.generate()) bad = copy.deepcopy(op) sig = str(bad["sig"]) bad["sig"] = sig[:-1] + ("0" if sig[-1] != "0" else "1") assert not verifies(bad) def test_tampered_prev_rejected(): kp = KeyPair.generate() op = _signed(kp, prev=["op:" + "ab" * 32]) bad = copy.deepcopy(op) bad["prev"] = [] assert not verifies(bad) def test_missing_fields_rejected(): for field in ("sig", "id"): op = _signed(KeyPair.generate()) bad = copy.deepcopy(op) bad.pop(field, None) assert not verifies(bad), f"op without {field!r} must not verify" def test_signing_is_deterministic(): kp = KeyPair.from_seed(b"\x07" * 32) op1 = _signed(kp) op2 = _signed(kp) assert op1["id"] == op2["id"] assert op1["sig"] == op2["sig"] def test_different_authors_produce_different_ids(): payload = {"source": "test", "body": "same payload"} op1 = _signed(KeyPair.from_seed(b"\x01" * 32), payload=payload) op2 = _signed(KeyPair.from_seed(b"\x02" * 32), payload=payload) assert op1["id"] != op2["id"] def test_seed_key_derivation_deterministic(): a = KeyPair.from_seed(b"\x09" * 32) b = KeyPair.from_seed(b"\x09" * 32) c = KeyPair.from_seed(b"\x0a" * 32) assert a.public_key_hex == b.public_key_hex assert a.public_key_hex != c.public_key_hex def test_raw_sign_verify_roundtrip(): kp = KeyPair.generate() msg = b"what do you know about me and why?" sig = kp.sign(msg) assert verify_ok(kp.public_key_hex, sig, msg) assert not verify_ok(kp.public_key_hex, sig, msg + b"!")