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:
@@ -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.
|
||||
|
||||
@@ -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")` |
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user