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>
This commit is contained in:
Aaron D. Lee
2026-04-02 15:05:13 -04:00
parent 6325e86873
commit 490f9d4a1d
188 changed files with 4588 additions and 2017 deletions

View File

@@ -10,7 +10,7 @@ The attestation chain is an append-only sequence of signed records stored locall
offline device. Each record includes a hash of the previous record, forming a tamper-evident
chain analogous to git commits or blockchain blocks.
The chain wraps existing Verisoo attestation records. A Verisoo record's serialized bytes
The chain wraps existing Attest attestation records. A Attest record's serialized bytes
become the input to `content_hash`, preserving the original attestation while adding
ordering, entropy witnesses, and chain integrity guarantees.
@@ -24,8 +24,8 @@ ordering, entropy witnesses, and chain integrity guarantees.
| `record_id` | 1 | byte string | 16 bytes | UUID v7 (RFC 9562). Time-ordered unique identifier. |
| `chain_index` | 2 | unsigned int | 8 bytes max | Monotonically increasing, 0-based. Genesis record is index 0. |
| `prev_hash` | 3 | byte string | 32 bytes | SHA-256 of `canonical_bytes(previous_record)`. Genesis: `0x00 * 32`. |
| `content_hash` | 4 | byte string | 32 bytes | SHA-256 of the wrapped content (e.g., Verisoo record bytes). |
| `content_type` | 5 | text string | variable | MIME-like type identifier. `"verisoo/attestation-v1"` for Verisoo records. |
| `content_hash` | 4 | byte string | 32 bytes | SHA-256 of the wrapped content (e.g., Attest record bytes). |
| `content_type` | 5 | text string | variable | MIME-like type identifier. `"attest/attestation-v1"` for Attest records. |
| `metadata` | 6 | CBOR map | variable | Extensible key-value map. See §2.1. |
| `claimed_ts` | 7 | integer | 8 bytes max | Unix timestamp in microseconds (µs). Signed integer to handle pre-epoch dates. |
| `entropy_witnesses` | 8 | CBOR map | variable | System entropy snapshot. See §3. |
@@ -41,7 +41,7 @@ The `metadata` field is an open CBOR map with text string keys. Defined keys:
| `"backfilled"` | bool | `true` if this record was created by the backfill migration |
| `"caption"` | text | Human-readable description of the attested content |
| `"location"` | text | Location name associated with the attestation |
| `"original_ts"` | integer | Original Verisoo timestamp (µs) if different from `claimed_ts` |
| `"original_ts"` | integer | Original Attest timestamp (µs) if different from `claimed_ts` |
| `"tags"` | array of text | User-defined classification tags |
Applications may add custom keys. Unknown keys must be preserved during serialization.
@@ -214,24 +214,24 @@ This file is a performance optimization — the canonical state is always deriva
### 6.3 File Locations
```
~/.soosef/chain/
~/.fwmetadata/chain/
chain.bin Append-only record log
state.cbor Chain state checkpoint
```
Paths are defined in `src/soosef/paths.py`.
Paths are defined in `src/fieldwitness/paths.py`.
## 7. Migration from Verisoo-Only Attestations
## 7. Migration from Attest-Only Attestations
Existing Verisoo attestations in `~/.soosef/attestations/` are not modified. The chain
is a parallel structure. Migration is performed by the `soosef chain backfill` command:
Existing Attest attestations in `~/.fwmetadata/attestations/` are not modified. The chain
is a parallel structure. Migration is performed by the `fieldwitness chain backfill` command:
1. Iterate all records in Verisoo's `LocalStorage` (ordered by timestamp)
1. Iterate all records in Attest's `LocalStorage` (ordered by timestamp)
2. For each record, compute `content_hash = SHA-256(record.to_bytes())`
3. Create a chain record with:
- `content_type = "verisoo/attestation-v1"`
- `claimed_ts` set to the original Verisoo timestamp
- `metadata = {"backfilled": true, "original_ts": <verisoo_timestamp>}`
- `content_type = "attest/attestation-v1"`
- `claimed_ts` set to the original Attest timestamp
- `metadata = {"backfilled": true, "original_ts": <attest_timestamp>}`
- Entropy witnesses collected at migration time (not original time)
4. Append to chain
@@ -245,8 +245,8 @@ The `content_type` field identifies what was hashed into `content_hash`. Defined
| Content Type | Description |
|---|---|
| `verisoo/attestation-v1` | Verisoo `AttestationRecord` serialized bytes |
| `soosef/raw-file-v1` | Raw file bytes (for non-image attestations, future) |
| `soosef/metadata-only-v1` | No file content; metadata-only attestation (future) |
| `attest/attestation-v1` | Attest `AttestationRecord` serialized bytes |
| `fieldwitness/raw-file-v1` | Raw file bytes (for non-image attestations, future) |
| `fieldwitness/metadata-only-v1` | No file content; metadata-only attestation (future) |
New content types may be added without changing the record format version.

View File

@@ -22,7 +22,7 @@ version, structured binary payload.
```
Offset Size Field
────── ───────── ──────────────────────────────────────
0 8 magic: b"SOOSEFX1"
0 8 magic: b"FIELDWITNESSX1"
8 1 version: uint8 (1)
9 4 summary_len: uint32 BE
13 var chain_summary: CBOR (see §3)
@@ -39,7 +39,7 @@ All multi-byte integers are big-endian. The total bundle size is:
### Parsing Without Decryption
To audit a bundle without decryption, read:
1. Magic (8 bytes) — verify `b"SOOSEFX1"`
1. Magic (8 bytes) — verify `b"FIELDWITNESSX1"`
2. Version (1 byte) — verify `1`
3. Summary length (4 bytes BE) — read the next N bytes as CBOR
4. Chain summary — verify signature, inspect metadata
@@ -132,7 +132,7 @@ For each recipient, the DEK is wrapped using X25519 ECDH + HKDF + AES-256-GCM:
2. derived_key = HKDF-SHA256(
ikm=shared_secret,
salt=bundle_id, # binds to this specific bundle
info=b"soosef-dek-wrap-v1",
info=b"fieldwitness-dek-wrap-v1",
length=32
)
3. wrapped_dek = AES-256-GCM_Encrypt(
@@ -185,7 +185,7 @@ A recipient decrypts a bundle:
2. Find own pubkey in recipients array
3. shared_secret = X25519_ECDH(recipient_x25519_private, sender_x25519_public)
(sender_x25519_public derived from summary.signer_pubkey)
4. derived_key = HKDF-SHA256(shared_secret, salt=bundle_id, info=b"soosef-dek-wrap-v1")
4. derived_key = HKDF-SHA256(shared_secret, salt=bundle_id, info=b"fieldwitness-dek-wrap-v1")
5. dek = AES-256-GCM_Decrypt(derived_key, wrap_nonce, wrapped_dek, aad=bundle_id)
6. compressed = AES-256-GCM_Decrypt(dek, nonce, ciphertext, aad=summary_bytes)
7. records_cbor = zstd.decompress(compressed)
@@ -240,11 +240,11 @@ These are two different trees:
## 6. Steganographic Embedding
Bundles can optionally be embedded in JPEG images using stegasoo's DCT steganography:
Bundles can optionally be embedded in JPEG images using stego's DCT steganography:
```
1. bundle_bytes = create_export_bundle(chain, start, end, private_key, recipients)
2. stego_image = stegasoo.encode(
2. stego_image = stego.encode(
carrier=carrier_image,
reference=reference_image,
file_data=bundle_bytes,
@@ -256,14 +256,14 @@ Bundles can optionally be embedded in JPEG images using stegasoo's DCT steganogr
Extraction:
```
1. result = stegasoo.decode(
1. result = stego.decode(
carrier=stego_image,
reference=reference_image,
passphrase=passphrase,
channel_key=channel_key
)
2. bundle_bytes = result.file_data
3. assert bundle_bytes[:8] == b"SOOSEFX1"
3. assert bundle_bytes[:8] == b"FIELDWITNESSX1"
```
### 6.1 Capacity Considerations
@@ -299,7 +299,7 @@ recipient, the creator needs only their public key (no shared secret setup requi
Recipients' Ed25519 public keys can be obtained via:
- Direct exchange (QR code, USB transfer, verbal fingerprint verification)
- Federation server identity registry (when available)
- Verisoo's existing `peers.json` file
- Attest's existing `peers.json` file
### 7.3 Self-Encryption
@@ -310,7 +310,7 @@ This allows them to decrypt their own exports (e.g., when restoring from backup)
| Error | Cause | Response |
|---|---|---|
| Bad magic | Not a SOOSEFX1 bundle | Reject with `ExportError("not a SooSeF export bundle")` |
| Bad magic | Not a FIELDWITNESSX1 bundle | Reject with `ExportError("not a FieldWitness export bundle")` |
| Bad version | Unsupported format version | Reject with `ExportError("unsupported bundle version")` |
| Signature invalid | Tampered summary or wrong signer | Reject with `ExportError("bundle signature verification failed")` |
| No matching recipient | Decryptor's key not in recipients list | Reject with `ExportError("not an authorized recipient")` |

View File

@@ -19,7 +19,7 @@ the underlying attestation data.
| Term | Definition |
|---|---|
| **Bundle** | An encrypted export bundle (SOOSEFX1 format) containing chain records |
| **Bundle** | An encrypted export bundle (FIELDWITNESSX1 format) containing chain records |
| **STH** | Signed Tree Head — a server's signed commitment to its current Merkle tree state |
| **Receipt** | A server-signed proof that a bundle was included in its log at a specific time |
| **Inclusion proof** | Merkle path from a leaf (bundle hash) to the tree root |
@@ -106,7 +106,7 @@ POST /v1/submit
**Request body**: Raw bundle bytes (application/octet-stream)
**Processing**:
1. Verify magic bytes `b"SOOSEFX1"` and version
1. Verify magic bytes `b"FIELDWITNESSX1"` and version
2. Parse chain summary
3. Verify `bundle_sig` against `signer_pubkey`
4. Compute `bundle_hash = SHA-256(0x00 || bundle_bytes)`
@@ -215,7 +215,7 @@ GET /v1/entries?start={s}&end={e}
0: tree_index, # uint
1: bundle_hash, # bytes[32]
2: chain_summary, # CBOR map (from bundle, unencrypted)
3: encrypted_blob, # bytes — full SOOSEFX1 bundle
3: encrypted_blob, # bytes — full FIELDWITNESSX1 bundle
4: receipt_ts, # int — Unix µs when received
}
```
@@ -488,8 +488,8 @@ CREATE INDEX idx_bundles_receipt_ts ON bundles(receipt_ts);
"server_id": "my-server.example.org",
"host": "0.0.0.0",
"port": 8443,
"data_dir": "/var/lib/soosef-federation",
"identity_key_path": "/etc/soosef-federation/identity/private.pem",
"data_dir": "/var/lib/fieldwitness-federation",
"identity_key_path": "/etc/fieldwitness-federation/identity/private.pem",
"peers": [
{
"url": "https://peer1.example.org:8443",

View File

@@ -6,7 +6,7 @@
## 1. Problem Statement
SooSeF operates offline-first: devices create Ed25519-signed attestations without network
FieldWitness operates offline-first: devices create Ed25519-signed attestations without network
access. This creates two fundamental challenges:
1. **Timestamp credibility** — An offline device's clock is untrusted. An adversary with
@@ -55,7 +55,7 @@ protecting content confidentiality even from the distribution infrastructure.
────────────── ─────── ────────
┌──────────┐ ┌──────────────┐ ┌──────────┐ USB/SD ┌──────────┐ ┌────────────┐
Verisoo │────>│ Hash Chain │────>│ Export │───────────────>│ Loader │────>│ Federation │
Attest │────>│ Hash Chain │────>│ Export │───────────────>│ Loader │────>│ Federation │
│ Attest │ │ (Layer 1) │ │ Bundle │ │ (App) │ │ Server │
└──────────┘ └──────────────┘ │ (Layer 2)│ └────┬─────┘ └─────┬──────┘
└──────────┘ │ │
@@ -80,8 +80,8 @@ Each attestation is wrapped in a chain record that includes:
- Entropy witnesses (system uptime, kernel state) that make timestamp fabrication expensive
- An Ed25519 signature over the entire record
The chain lives on the offline device at `~/.soosef/chain/`. It wraps existing Verisoo
attestation records — the Verisoo record's bytes become the `content_hash` input.
The chain lives on the offline device at `~/.fwmetadata/chain/`. It wraps existing Attest
attestation records — the Attest record's bytes become the `content_hash` input.
**See**: [chain-format.md](chain-format.md)
@@ -94,7 +94,7 @@ A range of chain records is packaged into a portable bundle:
4. An unencrypted `chain_summary` (record count, hash range, Merkle root, signature) allows
auditing without decryption
Bundles can optionally be embedded in JPEG images via stegasoo's DCT steganography,
Bundles can optionally be embedded in JPEG images via stego's DCT steganography,
making them indistinguishable from normal photos on a USB stick.
**See**: [export-bundle.md](export-bundle.md)
@@ -123,13 +123,13 @@ The loader never needs signing keys — bundles are already signed. It is a tran
## 4. Key Domains
SooSeF maintains strict separation between two cryptographic domains:
FieldWitness maintains strict separation between two cryptographic domains:
| Domain | Algorithm | Purpose | Key Location |
|---|---|---|---|
| **Signing** | Ed25519 | Attestation signatures, chain records, bundle summaries | `~/.soosef/identity/` |
| **Signing** | Ed25519 | Attestation signatures, chain records, bundle summaries | `~/.fwmetadata/identity/` |
| **Encryption** | X25519 + AES-256-GCM | Bundle payload encryption (envelope) | Derived from Ed25519 via birational map |
| **Steganography** | AES-256-GCM (from factors) | Stegasoo channel encryption | `~/.soosef/stegasoo/channel.key` |
| **Steganography** | AES-256-GCM (from factors) | Stego channel encryption | `~/.fwmetadata/stego/channel.key` |
The signing and encryption domains share a key lineage (Ed25519 → X25519 derivation) but
serve different purposes. The steganography domain remains fully independent — it protects
@@ -215,7 +215,7 @@ more credible timestamps. Frequent USB sync trips shrink the window.
## 10. File Layout
```
src/soosef/federation/
src/fieldwitness/federation/
__init__.py
models.py Chain record and state dataclasses
serialization.py CBOR canonical encoding
@@ -241,7 +241,7 @@ src/soosef/federation/
permissions.py Access control
config.py Server configuration
~/.soosef/
~/.fwmetadata/
chain/ Local hash chain
chain.bin Append-only record log
state.cbor Chain state checkpoint