# 證據格式

Canonical page: https://phishdestroy.github.io/TgWitness/zh-Hant/docs/reference/evidence/

簽名 ZIP 檔案的內容、記錄類型、處理回執、使用者檔案、異地備份及離線驗證。

觀察結果不等同於結論。發送者 ID 並非現實世界的身分。 帳戶可見的服務更新是資料來源；Telegram 並未為第三方調查人員 對這些儲存的記錄進行加密簽名。

## 何謂「簽名」

| 資料 | 保護 | 應保留的內容 |
| --- | --- | --- |
| 每則日誌記錄，包括操作事件 | 每個事件皆包含 SHA-256 鏈及 Ed25519 簽名 | 原始 `events.jsonl`（可信金鑰指紋）及保留的檢查點 |
| 證據 ZIP 檔案內容 | `manifest.sig` 對規範性清單進行簽名，該清單列出了內容檔案的雜湊值 | 完整的 ZIP 檔案，包含其清單檔與簽名檔 |
| 保留的附件 | 簽名過的 `media.saved` 事件中包含 SHA-256 值與位元組計數；簽名過的 ZIP 清單中包含檔案雜湊值 | 經驗證 ZIP 檔案內的原始位元組 |
| 檢查點 | 針對日誌長度、開頭雜湊值、公鑰及建立時間進行 Ed25519 簽名 | 在爭議性變更發生前保留的獨立副本 |
| 執行收據 | 分別簽名的收集器測量數據；亦包含於經簽名的 `process.started` 事件中 | 收據及相關日誌；相關數據均為使用者自行申報 |
| 獨立的 CSV 或 HTML 檔案 | 不包含獨立的加密簽名；僅憑事件雜湊值並不足夠 | 請使用經驗證 ZIP 檔案中的原始內容進行完整性檢查 |
| 由瀏覽器產生的螢幕截圖、列印文件或 PDF 檔案 | 衍生作品上不得出現 TgWitness 簽名 | 請保留原始的已驗證套件，並記錄衍生版本的製作過程 |

該收集器會在生成每種匯出格式之前，先驗證日誌的簽名。 此來源驗證不會在另行下載的 CSV 或 HTML 檔案上產生簽名。同樣地，有效的 JSONL 事件簽名也不會提供附件 位元組，亦無法獨立證明已提供完整的觀察到的日誌內容。

## 調查交接

1. 請在案件筆記中記錄案件編號、操作人員、Telegram 帳戶編號、選定的 來源、蒐集政策及蒐集期間。TgWitness 負責保存觀察結果；案件指派與保管紀錄則由 應用程式外部管理。請透過 [即時核准程序](https://github.com/phishdestroy/TgWitness/blob/main/docs/internal/LIVE_ACCEPTANCE.md)，確認預定的蒐集設定是否符合要求。
2. 在依賴 其匯出內容之前，請先獨立保留工作區的公鑰指紋。在收集過程中，請將簽名檢查點儲存於收集器之外。 記錄中斷情況以及該帳戶可見性的限制。
3. 匯出**經簽名的 ZIP** 檔案並保留經簽名的執行收據。請在 匯出日誌邊界處或之前設定檢查點。若檢查點設定在較後位置，系統將能正確地拒絕 較早的匯出檔案，因其長度不足。匯出內容包含完整的工作區日誌， 無論面板篩選條件為何；請在分享前審閱相關資料。
4. 請收件者使用可信的驗證器副本，並搭配 獨立提供的指紋／檢查點，來驗證 ZIP 檔案。請從一個可信的檢查點開始，並確保 其 Python 環境已啟動：

   ```
   python -m tgwitness.verify evidence.zip --fingerprint TRUSTED_PUBLIC_KEY_SHA256 --checkpoint retained-checkpoint.json
   ```

   當兩個輸入皆已提供且被接受時，成功產出的報告將顯示 `key_pinned: true` 和 `checkpoint_checked: true` 。請保留驗證結果。

   ```
   For a handover requiring the final exported record to be witnessed, use
   `--witness witness.zip --witness-fingerprint TRUSTED_WITNESS_SHA256 --require-witness`.
   This refuses an export with an unwitnessed tail. See the
   [witness handover procedure](WITNESS.md#require-a-witnessed-export-boundary).
   ```

   1. 請在您的保管紀錄中記錄檔案的 SHA-256 值、匯出邊界、傳輸日期、發送者及 收件者。請保持原始封包完整，並僅 使用副本進行操作。請分享證據檔案，而非應用程式的私有資料 目錄，該目錄亦包含帳戶會話及簽名密鑰。 傳輸時，可將匯出檔案放入受密碼保護的 AES-256 ZIP 壓縮檔中。該 密碼僅用於保護機密性，且不會被儲存；收件者解壓縮 檔案後，即可驗證其中未經變更的簽名匯出檔案。
   2. 若需獨立的時間錨定，請遵循 [RFC 3161](https://phishdestroy.github.io/TgWitness/zh-Hant/docs/reference/timestamp/)程序，並保留請求、檢查點、可信 CA 資訊以及經驗證的授權機構回應。僅準備請求 並無法取得時間戳記。

成功的驗證可確立其相對於可信收集器 金鑰及保留邊界的完整性。原始真實性、完整的 Telegram 傳送紀錄、可信時間， 以及法律可採納性，均需另行提供證據。此版本尚未完成 Telegram 實際運作資格認證，亦未經過獨立的法證／安全評估。

被複製的收集器金鑰可對偽造的替代日誌進行簽名。若對照 獨立保留的檢查點或見證記錄進行驗證，即可偵測到早期前綴遭篡改； 僅靠簽名驗證則無法做到這一點。 「見證者」僅能修正收集器所呈現的內容，因此 無法獨立證實首個 外部檢查點之前所偽造紀錄的真實性。雲端部署報告會檢查配置及收集器回報的 測量值；但無法證實私密金鑰在初始化過程中從未被複製。

透過 Apprise 發送的通知包含工作區和聊天室名稱及數量；已刪除及編輯訊息的簡短 摘錄僅在操作員啟用該功能時才會加入 （預設為關閉）。這些僅為便利性提示，並非證據：無法保證送達， 且僅有日誌及其經簽名的匯出檔會記錄實際觀察到的內容。 若啟用工作區 代理伺服器，通知將沿用相同路徑傳輸；無法使用該路徑的服務，以及 MTProxy 路由，均將被拒絕。

## 事件規範化

雜湊輸入必須精確包含以下這些欄位：

```
seq, observed_at, source_at, kind, chat_id, message_id, actor_id, payload, previous_hash
```

請使用與 Python 相容的 JSON 格式進行序列化，採用 UTF-8 編碼、物件鍵值排序，不進行 ASCII 轉義，不包含無意義的空白字元，且不包含 NaN 或無窮大：

```
json.dumps(value, ensure_ascii=False, sort_keys=True,
           separators=(",", ":"), allow_nan=False).encode("utf-8")
```

這些位元的 SHA-256 值即為事件雜湊。簽名採用 Ed25519 演算法對 32 位元原始雜湊位元進行運算，隨後進行 Base64 編碼。第一個 previous_hash 為 64 個零。 序號從 1 開始，並以 1 為單位遞增。所有事件（包括運作 事件）共用同一條鏈。本系統不進行重複傳送抑制：重複 的觀察結果僅代表不同的收據，而非聲稱存在不同的 Telegram 操作。

Telegram 的原始日期為 ISO 字串；二進位欄位則轉為包含 bytes_base64 的物件。訊息內容、實體、媒體描述符、回覆元資料、反應， 以及服務動作，若已提供，皆會保留於原始訊息元資料中。

observed_at 為收集器的 UTC 牆鐘時間。source_at 為提供的 Telegram 日期 （如有）。刪除通知不會提供確切的刪除時間，因此 source_at 將保持為 null。傳輸接收順序未必等同於來源事件 的順序。歷史快照會使用較早的來源日期，但採用當前的觀察時間。

## 行為者語義

message.new 中的 actor_id 代表訊息發送者。message.edited 並未推斷出 編輯者；payload.sender_id 則記錄所觀察訊息的發送者。 刪除 actor_id 仍為 null。成員服務訊息會保留發送者與動作 類型，不會將每次移除都視為自願退出。原始參與者 更新會保留所提供的 actor_id。反應快照不會虛構變更行為者。

私人聊天室和一般群組的刪除通知僅包含訊息 ID。 這些聊天室共用一套適用於整個帳戶的 ID 序列，因此一個 ID 代表該 帳戶中的一則訊息。 當具有該 ID 的訊息先前已儲存於此日誌中時， 刪除操作將在該聊天室中以 message.deleted 形式記錄，並使用 payload.chat_resolution 作為方法名稱。從未被儲存過的 ID 則以 deletion.unscoped 形式保存，且不包含 chat_id；系統絕不會進行任何推測。 頻道和超級群組會針對每個聊天室分別編號訊息，且 絕不會以此方式進行配對。當被刪除的訊息是由被監測帳戶發送時， payload.deleted_message_was_outgoing 為 true；但仍未提供刪除者資訊。

## 涵蓋範圍：連續性、缺口及未被偵測的 ID

每個簽名 ZIP 檔案均包含 `coverage.json`，HTML 報告開頭會顯示相同的摘要，而 `verify.py` 會根據 `events.jsonl` 重新計算該摘要，而非直接信任該檔案：

- **從 X 到 Y 持續進行監測。**一個週期始於 `observation.started`，並 終止於暫停、停止、Telegram 連線中斷、登出、未經 正常關機的程序啟動，或監測期間超過三分鐘無任何記錄 （每分鐘會寫入一次心跳訊號，因此靜默表示程序已停止、凍結或 電腦進入休眠狀態）。當在**「參數」**中變更心跳間隔時，每次心跳 都會記錄此變更，且允許的靜默時間將調整為三個間隔，絕不低於三分鐘。
- **「缺口」**欄位列出各期間之間的時間間隔及其原因。若最後 一筆記錄處仍有未填補的缺口，則標記為 `not_resumed_by_last_record`。
- **未偵測到的訊息 ID。**針對以「每則聊天」為單位計數的頻道和超級群組， 在已偵測到的 ID 之間缺失的 ID 會以範圍形式列出。這些 ID 會被標示為 「*未偵測到*」—— 原因可能是已被刪除、從未送達，或超出儲存的歷史紀錄範圍 —— 絕不會直接標示為 「已刪除」。 私人聊天和一般群組共用全帳戶的序列號，因此其中的 缺口並無特殊意義，故不予列出。
- 聊天層級的資料缺口通知（例如 Telegram 在某個 頻道中要求恢復差異）將被計為備註；此類通知不會中斷整體涵蓋範圍。

「涵蓋範圍」描述了此資料收集工具所記錄的內容。這並不代表 Telegram 在 連續期間內傳送了每一則更新。

## 製作人

證據 ZIP 檔案、聊天記錄匯出檔、Witness 日誌以及 人員檔案的聲明中所包含的簽名清單中，皆載有 `producer` ：軟體名稱、版本、專案（PhishDestroy）、 官方網站及原始碼儲存庫。HTML 報告中亦顯示相同內容，而 `verify.py` 會將其列為已驗證項目之一。 在此欄位建立之前所製作的檔案，其驗證結果與 先前相同，且報告中會標示為未指明產生者。

## 人物簡介

系統會應要求，根據日誌中已有的 單一聊天記錄，建立一個個人檔案檔案（`tgwitness-dossier-v1`，JSON）；不會從 Telegram 擷取任何資料，且個人檔案內容絕不會 寫入日誌（啟用或停用個人檔案的動作會以 `dossier.changed` 形式記錄）。 針對每位擁有已儲存訊息的使用者，系統會列出其姓名與使用者名稱歷史紀錄、Telegram 標記 （機器人、詐騙、假帳號、付費帳號、已驗證、受限、已刪除）——以儲存當下的狀態為準——以及個人資料 照片 ID、來自本地頭像快取中與該頭像相似的頭像之 SHA-256 及感知雜湊（pHash，相容於 `imagehash.phash`）， 活動紀錄（第一則與最後一則訊息、 數量、已編輯及已刪除的分享、按 UTC 小時劃分的訊息）、誰回覆了誰、該人物曾發文的 中該人曾發文的對話，以及被提及的錢包（BTC、EVM、TRON、TON、LTC、 DOGE、XMR、SOL；Base58Check 地址已通過校驗和驗證）、電話號碼、使用者名稱及 連結，包括隱藏於文字後面的連結。

每個事實都會列出其來源的日誌記錄（`seq`），而 `record_hashes` 則包含 每個被引用的記錄的雜湊值。該陳述已使用工作區金鑰進行簽署：

```
python verify.py tgwitness-dossier-CHAT.json --fingerprint TRUSTED_SHA256 --evidence tgwitness-evidence.zip
```

會檢查簽名、驗證證據匯出檔本身（包含簽名、雜湊鏈及檔案）， 並確認每個被引用的記錄在同一工作區的該匯出檔中均完整無缺且未經修改。頭像雜湊值會標示為來自本地快取 （而非日誌記錄）。提及內容屬模式匹配——僅為待查證的線索，而非經核實的事實。 各聊天室皆可個別啟用個人檔案功能，可匯出為帶有密碼的 AES-256 加密 ZIP 檔案，且 當個人檔案功能關閉，或該聊天室從來源中移除時，相關資料即會被刪除。

## 獨立見證人

會將「證人日誌」（`tgwitness-witness-v1`）及其頻道貼文，與 透過 `verify.py --witness … --channel …` 匯出的證據進行比對；請參閱「[獨立證人」](https://phishdestroy.github.io/TgWitness/zh-Hant/docs/reference/witness/)。

## 清單與檢查點

manifest.json 包含格式、匯出時間、公鑰、指紋、日誌 長度、首部雜湊、範圍、產生者、限制，以及除 `manifest.json` 和 `manifest.sig` 本身以外 每個內容成員的 SHA-256 雜湊值。 manifest.sig 是對規範化 manifest JSON 進行 Base64 編碼的 Ed25519 簽名。 驗證器會拒絕處理意外出現或缺失的成員、名稱重複、檔案損毀、 序列/鏈路中斷、簽名無效，以及與提供的檢查點不匹配的情況。 它絕不會提取存檔路徑。它將聲明總未壓縮大小限制為 4 GiB； 若提高 **Parameters** 限制，`--max-gib N` 允許建立更大的存檔。

請從可信的檢出版本或經審核的獨立副本中取得驗證程式碼。位於不可信存檔中的驗證程式 屬於該存檔作者所提供的可執行內容； 其存在僅為便於移植，並非獨立的信任錨點。

檢查點會簽署一份聲明，其中包含公鑰、日誌長度、首部 雜湊值以及建立時間。驗證者可檢查後續的存檔是否包含 相同的前綴。在發生爭議性變更之前，請將檢查點保留在收集器之外： 若攻擊者同時控制存檔與檢查點，便可能將兩者一併替換。

來自未固定金鑰的有效簽名僅能確立內部一致性。 即使使用固定金鑰，若收集器或金鑰在記錄前已遭 洩露，亦無法證明來源內容的真實性。外部時間戳記與可信捕獲是 獨立的要求。TgWitness 不保證其證據可採納性或法證認證。

## 附件

檔案在 media/ 目錄下以 SHA-256 雜湊值作為位址。media.saved 事件會將雜湊值與 位元組長度連結至特定的訊息事件。原始檔案名稱屬於元資料，絕非 本機路徑。 媒體資料的位元組會以非同步方式下載，因此若訊息被立即刪除，可能會導致 無法擷取。系統會顯示以下結果：收集功能已停用、檔案過大／大小未知、下載失敗， 以及下載中斷。若程序發生當機，可能會留下 未被引用的部分檔案；這些檔案不會包含在匯出檔案中。

ZIP 匯出工具會根據檔案名稱驗證儲存的媒體檔案。JSONL、CSV 和 HTML 匯出格式會驗證事件簽名，但不包含媒體位元組。衍生報告 僅供方便使用；events.jsonl 才是具有權威性的事件表示形式。

## 持久的蒐集與處理收據 (0.2)

媒體任務及其訊息事件提交將整合於單一交易中。 該任務儲存 原始 TL 訊息描述符；經簽名的觀察記錄會綁定其 SHA-256 值、帳號 及大小政策。還原功能會在下載前驗證該綁定關係。系統絕不會擷取 當前訊息，也不會默默替換為較新的附件。中斷的任務 會重新嘗試執行，而永久失敗則仍為明確的結果。

每個工作區皆擁有專屬的日誌及簽署身分。process.started 事件包含 執行 ID、本地時鐘、依賴項版本、原始檔雜湊值、初始帳戶／範圍， 以及擷取設定。心跳訊號包含執行 ID、單調時間及佇列狀態。 該流程收據亦可下載為一份獨立簽署的聲明。 檔案測量值為系統自行回報，並非可信賴的硬體驗證。

系統會在啟動時、監測期間每五分鐘（參見「**參數 → 每隔一次建立簽名檢查點**」），以及正常關機時，將自動檢查點保留於本地端。 啟動驗證與匯出時，會將日誌 與最新保留的本地檢查點進行比對。若檢查點仍存在，此機制可偵測到資料庫回滾； 但若操作者同時控制兩者，仍可將兩者一併替換。 獨立保留機制與外部可信時間戳記仍是兩項獨立的要求。

重新啟動後，系統將恢復已儲存的 Telegram 更新位置（`catch_up_from` 位於 `observation.started`），並請求補取遺漏的更新。定期重新檢查會記錄 `message.recovered` 以追蹤遺漏的訊息，並在 Telegram 不再 返回已儲存訊息時記錄 `message.missing_on_recheck`；後者並非刪除通知，亦未指明刪除者或時間。

### 站外備份與還原 (S3)

離線副本（「匯出」，預設為關閉）會將日誌片段、保留檔案、檢查點 及其時間戳記，以及完成的聊天記錄匯出上傳至 S3 相容儲存空間，並在 AES-256-GCM 加密，並使用儲存於工作區金庫中的金鑰；若儲存桶支援，則會套用 Object Lock 保留機制。若未下載金鑰，則無法讀取該副本。請使用 `python -m tgwitness.replica restore --key-file KEY.json --out DIR` 進行還原與驗證。

還原功能需要一個空的普通資料夾。下載的檔案及解密後的物件會暫存於私有 臨時儲存空間中，直至整個日誌及每個保留的檢查點均通過驗證為止。不安全的物件 路徑、不一致的區段範圍，以及長度短於檢查點的日誌均會被拒絕；驗證失敗時 將不會發布任何還原檔案。 保留檢查會將回傳的「物件鎖定」 模式與日期，與上傳時傳送的截止時間進行比對（容許一秒的四捨五入誤差）。已過期、 無效或縮短的截止時間均不視為已驗證。狀態會回報該次上傳過程中 讀取到保留狀態的抽樣物件。

複製進度會根據目標位置及加密金鑰進行範圍限定。若變更端點、儲存桶 或前綴，系統將開始將本機保留的日誌、媒體檔案及檢查點完整複製至 該目標位置。舊有的未限定範圍進度將予以保留，但在升級後會重新執行一次。歷史 聊天記錄匯出檔若已從本機發送箱中移除，仍會保留在原先的目標位置。

若本地媒體檔案遺失或損毀，系統會將其標記為當次處理的錯誤，並將媒體游標停留在 該處之前，以便在下一次處理時重新嘗試讀取該檔案；其後面的檔案、檢查點及匯出內容仍會 照常複製。還原過程會 將所有已保存媒體的參照與解密後的雜湊值及記錄的大小進行比對。 來自中斷上傳的 相同重疊日誌區段將被去重；衝突的重疊區段則會被拒絕。 上傳過程中建立的檢查點會被延遲，直到其所涵蓋的日誌記錄已被 複製為止，因此完成的處理階段不會在日誌之前發布檢查點。

### 串流匯出快照

串流匯出採用穩定的 SQLite 讀取快照。當下載完成時，其起始點可能比 當前的即時日誌更早。在此快照之後建立的檢查點 可能會正確地顯示該存檔較短；請與位於匯出前綴處或之前的檢查點 進行比較，而非任意的較晚檢查點。

## RFC 3161 準備與回應驗證 (0.2)

「證據模式」（預設為關閉）僅會透過工作區路徑（絕不經由 MTProxy），將每個簽名檢查點的 SHA-256 雜湊值傳送至列出的 RFC 3161 權威機構及 OpenTimestamps 日曆。 回覆內容將以 `checkpoints/anchors/` 格式保存、匯出，並由 `verify.py` 進行驗證（`--tsa-ca` 負責 驗證 TSA 簽名；使用 `ots upgrade` 和 `ots verify` 確認 OpenTimestamps 證明）。

該面板會準備一個請求存檔，其中包含標準的 checkpoint.json 位元組、 SHA-256 訊息指紋以及 timestamp.tsq 中的隨機非ce值、校驗和與操作說明。 此過程不會發出任何網路請求。此準備程序並非可信時間戳記。

`python -m tgwitness.anchor verify` 會對 OpenSSL 進行兩次 ts 驗證：一次針對檢查點 位元組，一次針對原始請求。必須明確指定可信賴的 CA 檔案； 內建憑證不會自動被視為可信。在命令列介面（CLI）中， 請求檢查為可選功能，因此請保留並提供 --request 參數以檢查非ce值。結果會顯示 是否已進行檢查。收集器的檢查點簽名與日誌仍須 透過可信的收集器指紋分別進行驗證。

目前的驗證器不會擷取撤銷資訊，亦未實作 憑證過期後的長期歸檔驗證。請勿將成功的簽名 驗證等同於合格的時間戳記或法律上的可採納性。開發期間並未聯繫任何真正的獨立 時間服務機構（TSA）。請參閱[程序說明](https://phishdestroy.github.io/TgWitness/zh-Hant/docs/reference/timestamp/)。
