簽章重放攻擊
簽章重放攻擊是在一個本不該再次有效的情境中,重用一個有效簽署的訊息。鏈下簽章就像一張簽好的支票:它授權一個動作,而一個接受簽章的合約(用於免 gas 授權、元交易或鏈下訂單),必須確保每張支票只能被兌現一次、且只在一個地方。若被簽署的資料沒有釘死它是給哪個合約、哪條鏈、哪一次使用,攻擊者就能把同一個簽章提交兩次、或在另一條鏈上提交,而合約會再次認可它。
有三個不同的重放面,各自靠把正確的值綁入被簽訊息來封堵。對同一個合約重用同一個簽章,靠合約標記為已消耗的每使用者 nonce 來阻止。跨鏈重放,例如在鏈分裂之後、或在一條共用相同帳號的分叉上,靠納入 chain id 來阻止。把給某合約的簽章拿去另一個合約重放,靠納入驗證合約自身的地址來阻止。EIP-712 型別化資料標準,正是把 chain id、合約地址與一個 domain 字串打包成單一的 domain separator,好讓這三者一次全被綁定。
更微妙的陷阱是 ECDSA 簽章可延展性。對任何有效簽章 (r, s),在同一訊息上都存在第二個有效簽章 (r, n 減 s),其中 n 是曲線階。若合約把原始簽章位元組當成唯一性鍵,例如把簽章本身記為已用,攻擊者只要翻轉 s,就能產出一個看起來不同卻同樣有效的簽章並重放該動作。修法是把唯一性建立在 nonce 或訊息雜湊之上、而非簽章之上,並要求標準的低 s 形式(如 OpenZeppelin 的 ECDSA 函式庫所做的)。
bytes32 digest = keccak256(abi.encode(
msg.sender,
amount,
nonces[msg.sender]++, // single-use
block.chainid, // chain-bound
address(this) // contract-bound
));
require(ECDSA.recover(digest, sig) == signer, "bad sig");把 nonce、chain id 與合約綁進被簽署的摘要。
ECDSA 具可延展性:每個有效的 (r, s) 都有第二個有效的 (r, n 減 s)。若你以簽章本身的位元組來標記其為已用,攻擊者就翻轉 s 並重放。請把唯一性綁在 nonce 上,而非原始簽章上。