Implement 7 field-scenario feature requests
Some checks failed
CI / lint (push) Failing after 51s
CI / typecheck (push) Failing after 29s

1. Transport-aware stego encoding: --transport flag (whatsapp/signal/
   telegram/discord/email/direct) auto-selects DCT mode, pre-resizes
   carrier to platform max dimension, prevents payload destruction
   by messaging app recompression.

2. Standalone verification bundle: chain export ZIP now includes
   verify_chain.py (zero-dep verification script) and README.txt
   with instructions for courts and fact-checkers.

3. Channel-key-only export/import: export_channel_key() and
   import_channel_key() with Argon2id encryption (64MB, lighter
   than full bundle). channel_key_to_qr_data() for in-person
   QR code exchange between collaborators.

4. Duress/cover mode: configurable SSL cert CN via cover_name
   config (defaults to "localhost" instead of "SooSeF Local").
   SOOSEF_DATA_DIR already supports directory renaming. Killswitch
   PurgeScope.ALL now self-uninstalls the pip package.

5. Identity recovery from chain: find_signer_pubkey() searches chain
   by fingerprint prefix. append_key_recovery() creates a recovery
   record signed by new key with old fingerprint + cosigner list.
   verify_chain() accepts recovery records.

6. Batch verification: /verify/batch web endpoint accepts multiple
   files, returns per-file status (verified/unverified/error) with
   exact vs perceptual match breakdown.

7. Chain position proof in receipt: verification receipts (now
   schema v3) include chain_proof with chain_id, chain_index,
   prev_hash, and record_hash for court admissibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-04-01 20:26:03 -04:00
parent e50122e8e6
commit 7967d4b419
7 changed files with 442 additions and 4 deletions

View File

@@ -283,6 +283,58 @@ def attest_batch():
}
@bp.route("/verify/batch", methods=["POST"])
@login_required
def verify_batch():
"""Batch verification — accepts multiple image files.
Returns JSON with per-file verification results. Uses SHA-256
fast path before falling back to perceptual scan.
"""
files = request.files.getlist("images")
if not files:
return {"error": "No files uploaded"}, 400
results = []
for f in files:
filename = f.filename or "unknown"
try:
image_data = f.read()
result = _verify_image(image_data)
if result["matches"]:
best = result["matches"][0]
results.append({
"file": filename,
"status": "verified",
"match_type": best["match_type"],
"record_id": best["record"].short_id if hasattr(best["record"], "short_id") else "unknown",
"matches": len(result["matches"]),
})
else:
results.append({"file": filename, "status": "unverified", "matches": 0})
except Exception as e:
results.append({"file": filename, "status": "error", "error": str(e)})
verified = sum(1 for r in results if r["status"] == "verified")
unverified = sum(1 for r in results if r["status"] == "unverified")
errors = sum(1 for r in results if r["status"] == "error")
# Count by match type
exact = sum(1 for r in results if r.get("match_type") == "exact")
perceptual = verified - exact
return {
"total": len(results),
"verified": verified,
"verified_exact": exact,
"verified_perceptual": perceptual,
"unverified": unverified,
"errors": errors,
"results": results,
}
def _verify_image(image_data: bytes) -> dict:
"""Run the full verification pipeline against the attestation log.
@@ -460,10 +512,39 @@ def verify_receipt():
}
if safe_meta:
rec_entry["metadata"] = safe_meta
# Chain position proof — look up this attestation in the hash chain
try:
from soosef.config import SoosefConfig
from soosef.federation.chain import ChainStore
from soosef.federation.serialization import compute_record_hash
from soosef.paths import CHAIN_DIR
chain_config = SoosefConfig.load()
if chain_config.chain_enabled:
chain_store = ChainStore(CHAIN_DIR)
# Search chain for a record whose content_hash matches this attestation
content_hash_hex = getattr(record, "image_hashes", None)
if content_hash_hex and hasattr(content_hash_hex, "sha256"):
target_sha = content_hash_hex.sha256
for chain_rec in chain_store:
if chain_rec.content_hash.hex() == target_sha or chain_rec.metadata.get("attestor") == record.attestor_fingerprint:
rec_entry["chain_proof"] = {
"chain_id": chain_store.state().chain_id.hex() if chain_store.state() else None,
"chain_index": chain_rec.chain_index,
"prev_hash": chain_rec.prev_hash.hex(),
"record_hash": compute_record_hash(chain_rec).hex(),
"content_type": chain_rec.content_type,
"claimed_ts": chain_rec.claimed_ts,
}
break
except Exception:
pass # Chain proof is optional — don't fail the receipt
matching_records.append(rec_entry)
receipt = {
"schema_version": "2",
"schema_version": "3",
"verification_timestamp": verification_ts,
"verifier_instance": verifier_instance,
"queried_filename": image_file.filename,