fieldwitness/tests/test_serialization.py
Aaron D. Lee 490f9d4a1d Rebrand SooSeF to FieldWitness
Complete project rebrand for better positioning in the press freedom
and digital security space. FieldWitness communicates both field
deployment and evidence testimony — appropriate for the target audience
of journalists, NGOs, and human rights organizations.

Rename mapping:
- soosef → fieldwitness (package, CLI, all imports)
- soosef.stegasoo → fieldwitness.stego
- soosef.verisoo → fieldwitness.attest
- ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name)
- SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR
- SoosefConfig → FieldWitnessConfig
- SoosefError → FieldWitnessError

Also includes:
- License switch from MIT to GPL-3.0
- C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py
- README repositioned to lead with provenance/federation, stego backgrounded
- Threat model skeleton at docs/security/threat-model.md
- Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:05:13 -04:00

124 lines
4.5 KiB
Python

"""Tests for CBOR serialization of chain records."""
from __future__ import annotations
import hashlib
from fieldwitness.federation.models import AttestationChainRecord, ChainState, EntropyWitnesses
from fieldwitness.federation.serialization import (
canonical_bytes,
compute_record_hash,
deserialize_record,
serialize_record,
)
def _make_record(**overrides) -> AttestationChainRecord:
"""Create a minimal test record with sensible defaults."""
defaults = {
"version": 1,
"record_id": b"\x01" * 16,
"chain_index": 0,
"prev_hash": ChainState.GENESIS_PREV_HASH,
"content_hash": hashlib.sha256(b"test content").digest(),
"content_type": "test/plain",
"metadata": {},
"claimed_ts": 1_700_000_000_000_000,
"entropy_witnesses": EntropyWitnesses(
sys_uptime=12345.678,
fs_snapshot=b"\xab" * 16,
proc_entropy=256,
boot_id="test-boot-id",
),
"signer_pubkey": b"\x02" * 32,
"signature": b"\x03" * 64,
}
defaults.update(overrides)
return AttestationChainRecord(**defaults)
def test_canonical_bytes_deterministic():
"""Same record always produces the same canonical bytes."""
record = _make_record()
b1 = canonical_bytes(record)
b2 = canonical_bytes(record)
assert b1 == b2
def test_canonical_bytes_excludes_signature():
"""Canonical bytes must not include the signature field."""
record_a = _make_record(signature=b"\x03" * 64)
record_b = _make_record(signature=b"\x04" * 64)
assert canonical_bytes(record_a) == canonical_bytes(record_b)
def test_canonical_bytes_sensitive_to_content():
"""Different content_hash must produce different canonical bytes."""
record_a = _make_record(content_hash=hashlib.sha256(b"a").digest())
record_b = _make_record(content_hash=hashlib.sha256(b"b").digest())
assert canonical_bytes(record_a) != canonical_bytes(record_b)
def test_serialize_deserialize_round_trip():
"""A record survives serialization and deserialization intact."""
original = _make_record()
data = serialize_record(original)
restored = deserialize_record(data)
assert restored.version == original.version
assert restored.record_id == original.record_id
assert restored.chain_index == original.chain_index
assert restored.prev_hash == original.prev_hash
assert restored.content_hash == original.content_hash
assert restored.content_type == original.content_type
assert restored.metadata == original.metadata
assert restored.claimed_ts == original.claimed_ts
assert restored.signer_pubkey == original.signer_pubkey
assert restored.signature == original.signature
# Entropy witnesses
assert restored.entropy_witnesses is not None
assert restored.entropy_witnesses.sys_uptime == original.entropy_witnesses.sys_uptime
assert restored.entropy_witnesses.fs_snapshot == original.entropy_witnesses.fs_snapshot
assert restored.entropy_witnesses.proc_entropy == original.entropy_witnesses.proc_entropy
assert restored.entropy_witnesses.boot_id == original.entropy_witnesses.boot_id
def test_serialize_includes_signature():
"""Full serialization must include the signature."""
record = _make_record(signature=b"\xaa" * 64)
data = serialize_record(record)
restored = deserialize_record(data)
assert restored.signature == b"\xaa" * 64
def test_compute_record_hash():
"""Record hash is SHA-256 of canonical bytes."""
record = _make_record()
expected = hashlib.sha256(canonical_bytes(record)).digest()
assert compute_record_hash(record) == expected
def test_record_hash_changes_with_content():
"""Different records produce different hashes."""
a = _make_record(content_hash=hashlib.sha256(b"a").digest())
b = _make_record(content_hash=hashlib.sha256(b"b").digest())
assert compute_record_hash(a) != compute_record_hash(b)
def test_metadata_preserved():
"""Arbitrary metadata survives round-trip."""
meta = {"backfilled": True, "caption": "test photo", "tags": ["evidence", "urgent"]}
record = _make_record(metadata=meta)
data = serialize_record(record)
restored = deserialize_record(data)
assert restored.metadata == meta
def test_empty_entropy_witnesses():
"""Record with no entropy witnesses round-trips correctly."""
record = _make_record(entropy_witnesses=None)
data = serialize_record(record)
restored = deserialize_record(data)
assert restored.entropy_witnesses is None