關於合約帳戶有 3 個子任務,共同組成了 bad account 系列。本文是系列的第三個任務 Orderless Hashing。
分析#
這題和上一題一樣有 2 個合約,帳戶合約 GrandPharaoh 的構造器中指定了特定的公鑰。__validate__
中的get_multicall_hash
會對每個 Call 遞迴計算 hash,最後用 异或操作 (^) 組成一個 hash,hash 是個 u256,low * high 是最終的 messageHash。我們構造傳入簽名的 r 和 s 能被 ECDSA 驗證。
需要執行的 Calls 是另一個合約的 RoyalSpear.equip (0),限制了只能被帳戶合約 GrandPharaoh 調用。
由於沒有私鑰,需要找到 ECDSA 中的漏洞去破解。
過程#
首先需要看看 Calls 列表計算的無序多簽最終是怎樣的,可以是 [equip(0)]
,是[equip(1),equip(0)]
或者更多,只需要最後一個值是 equip (0) 就行。
這一部分可以用 snforge 寫 cairo 的 test,在帳戶合約中添加(multicall_hash.low.into() * multicall_hash.high.into()).print();
讀取。
很明顯能發現,[equip(0),equip(0)]
對應的 hash 是 0,可以從此入手。
接下來深入 check_ecdsa_signature 中,看看具體是怎麼進行簽名驗證的。
註釋掉use ecdsa::check_ecdsa_signature;
,把源碼貼上到 test 中,方便在必要的地方添加 print。
首先注意到是 let zG: EcPoint = gen_point.mul(message_hash);
,由於 message_hash 是 0(無窮點),橢圓曲線中 P*0=0,所以 zG 是 0。如果用以下代碼檢查,會輸出 'zG_x not exist',因為無窮點是沒有 x 坐標的。
match zG.try_into() {
Option::Some(pt) => {
let (x, _) = ec::ec_point_unwrap(pt);
'zG_x'.print();
x.print();
},
Option::None => { 'zG_x not exist'.print(); },
};
註釋中寫清楚了驗證的公式是 (zG +/- rQ).x = sR.x
,對應 zG + rQ
的代碼是:
match (zG + rQ).try_into() {
Option::Some(pt) => {
let (x, _) = ec::ec_point_unwrap(pt);
if (x == sR_x) {
return true;
}
},
Option::None => {},
};
其中 zG 是 0,所以需要 rQ.x = sR.x
,r 是傳入的 sigature.r
,Q 是公鑰對應曲線上的點,s 是傳入的sigature.r
,R 是 r 對應曲線上的點。
很明顯只需要 r=s 且 Q=R 就可以通過驗證。其中公鑰可以在區塊鏈瀏覽器的部署交易中找到。
最後和上題同樣方法使用 Starknet.js 中的 invokeFunction 傳入特定 signature 和 calls 完成任務。
總結#
如果深入學習了 ECDSA,知道橢圓曲線的運算規則,這題是很容易找到答案的。合約的多簽設計太簡單,可以利用簽名重放進行攻擊。
就此 Account 部分的三題就全部完成了。整個 Cairo 系列還有一題用 Cairo 寫虛擬機的不打算做了,我討厭算法題。未來會更新在 Starknet 實際開發中遇到的有趣的東西,敬請期待。