ArcSign v1.5.0 對簽章安全做了兩個改變:把黑名單檢查從 Pro 釋出給所有人免費,並把真正的把關移到後端、動私鑰之前。這篇文章拆解這兩個決定背後的工程與理由。
一句話總結
惡意地址 / OFAC 黑名單檢查現在對所有人免費、離線可用(嵌進 app 的種子);當你簽往黑名單地址時,是後端在動私鑰前拒簽,除非你明確知情同意——前端的 checkbox 只是呈現,不是把關。
為什麼黑名單不該是付費功能
ArcSign 的交易安全檢查原本由兩部分組成:黑名單檢查與交易模擬。在 v1.4.0,這兩者一起被放在 Pro 門檻後面——免費使用者兩個都拿不到。
但這兩件事的成本結構完全不同:
- 黑名單檢查是純記憶體查表。給定一個地址,查它在不在一份已知惡意 / 制裁地址的清單裡。零成本、不需 API key、不需連網。
- 交易模擬需要呼叫第三方節點去模擬交易結果,有實際成本,需要 Alchemy key。
把零成本的黑名單檢查鎖在付費牆後面沒有道理——這是基礎安全功能,所有人都該有。所以 v1.5.0 把黑名單檢查移出 Pro 門檻,模擬留在 Pro。一個免費使用者現在簽往一個 OFAC 地址時,會看到完整的黑名單警告;只有「模擬這筆交易會發生什麼」仍是 Pro 功能。
離線種子:第一次開啟、沒網路也能用
如果黑名單是「所有人都該有的基礎保護」,那它就不能依賴「先連網下載一份清單」——否則第一次開啟、或離線環境下就裸奔了。
所以 v1.5.0 把一份黑名單種子直接嵌進 app(用 Go 的 go:embed),在程式啟動時同步載入。這份種子包含:
- OFAC 制裁地址(公領域資料)
- MIT 授權的惡意地址清單(複用自 MEW 的 ethereum-lists 與 Revoke 的 approval-exploit-list)
這意味著黑名單檢查在你第一次插上 USB、完全離線的狀態下就能運作。之後若有網路,線上更新會抓一份更完整的清單合併上去——注意是合併,不是覆蓋,嵌入的種子永遠保留作為離線保底。
(授權細節:我們刻意只用 MIT 與公領域的資料來源。ScamSniffer 的 GPL 資料雖然品質好,但授權不相容,所以排除在外——開源專案的授權衛生很重要。)
最關鍵的設計:安全門在後端,不在前端
這是整個功能最重要、也最容易做錯的地方。
一個直覺的做法是:在簽章對話框上放一個「目標地址在黑名單上,確定要簽嗎?」的 checkbox,使用者不勾就把簽章按鈕 disable 掉。這是錯的——或者更精確地說,這只是 UX,不是安全保證。
為什麼?因為前端的任何防線都可以被繞過:
- 改 JavaScript
- 直接呼叫底層 FFI
- 未來某條漏接 checkbox 的程式路徑
一個按鈕的 disabled 屬性,擋不住一個真的想繞過的人,也擋不住一個有 bug 的程式路徑。真正「架構上無法繞過」的安全門,必須在後端、在私鑰真正被使用的那一行之前。
所以 ArcSign 的設計是:當你要簽一筆交易時,後端的 SignTransaction 在動私鑰、派生金鑰之前,強制跑一次黑名單檢查。如果命中黑名單、而且你沒有明確帶上「我了解風險」的旗標,後端直接拒簽——回傳錯誤,私鑰完全不被觸碰。
簽章請求(含 acknowledgedRisk 旗標,可能為 false)
│
▼
┌──────────────── 後端 Go SignTransaction ────────────────┐
│ │
│ 黑名單檢查(嵌入種子,零成本) │
│ │ │
│ 命中黑名單? │
│ ┌────┴─────┐ │
│ 否 是 │
│ │ │ │
│ │ acknowledgedRisk == true? │
│ │ ┌────┴────┐ │
│ │ 是 否 │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ── 派生私鑰、簽章 ── ✗ 拒簽(回傳錯誤) │
│ (曝露 1–5ms) 私鑰完全不被觸碰 │
│ │ │ │
└────────┼───────────────────┼─────────────────────────────┘
▼ ▼
簽好的交易 前端顯示「我了解風險」checkbox
│(使用者勾選 → acknowledgedRisk=true)
└──→ 重送(旗標一路帶回後端)→ 放行
關鍵是這張圖的安全門整個在後端框框裡——前端的 checkbox 只出現在右下角,它的作用是產生 acknowledgedRisk 旗標送回後端,而不是自己決定簽不簽。
前端那個 checkbox 還在,但它的角色變了:它只是把後端算出來的「這筆需要確認」這個結論呈現給你,並把你的「我了解風險」決定帶回後端。前端不自己判斷危不危險(那是後端算的),它只讀後端的結論做呈現。
這是 ArcSign 一條貫穿全專案的鐵則:影響資產安全的判斷、決策、安全門,一律在後端;前端只讀後端結論做呈現。
知情同意:把關但不死鎖
「後端拒簽」聽起來很硬,但如果做得太死就會變成另一個問題:使用者明明知情、就是想送(也許他有正當理由送往一個被標記的地址),卻永遠簽不過去。那不是保護,是癱瘓。
所以這個安全門有一個知情同意的出路:當黑名單命中時,後端拒簽,但前端會顯示一個明確的「我了解風險」確認。使用者勾選後,這個 acknowledgedRisk 旗標會一路帶回後端,後端看到它就放行。
這裡有個工程陷阱值得記一筆:這個旗標必須完整地穿過每一層——前端 → Rust command → Go FFI。任何一層漏傳,黑名單交易就會變成「永遠拒簽,連勾了也簽不過」的死鎖。v1.5.0 在開發時為了確保每條簽章路徑(直接送款、WalletConnect、mint)的旗標都到得了後端,特別把這條 plumbing 仔細接過。把關要硬,但不能變成死路。
我們通知,但你決定
值得把 ArcSign 的立場說清楚,因為這牽涉到一個冷錢包該扮演什麼角色。
黑名單命中時,ArcSign 把資訊攤給你看、預設擋下、要求你明確確認——但最終決定權在你手上。我們不會替任何機構執行不可繞過的封鎖。制裁歸制裁、釣魚警告歸釣魚警告,我們的工作是確保你在知情的狀況下做決定,而不是替你做決定。
這是一個尊重使用者主權的設計。一個冷錢包的本質是「你完全掌控自己的資產」——如果它能在你不同意的狀況下永久阻止你動用自己的錢,那它就背叛了這個本質。所以我們的安全門是「通知 + 知情同意」,不是「強制封鎖」。
統一的簽章路徑
最後一個相關的工程改進:v1.5.0 把所有訊息簽章路徑(EIP-191 個人訊息、EIP-712 typed data)統一走同一個 XOR 分片的安全簽章器。在這之前有些路徑會以明文形式處理私鑰;現在沒有任何一條路徑這麼做了。同一個安全門、同一套金鑰處理,套用在每一條會動到私鑰的路徑上。
為什麼這個改動重要
釣魚攻擊的本質,是讓你在不知情的狀況下對一個惡意地址做一筆有害的操作。一道在你按下簽章前、在後端把關的黑名單檢查,是對這類攻擊最後、也最可靠的一道防線——因為它擋在私鑰被使用之前,而且架構上繞不過去。
把它釋出給所有人免費,意味著這道防線不再是付費使用者的特權。基礎安全,本來就該是基礎。
想看這一版的其他升級,回到 v1.5.0 版本總覽。