契約アカウントには 3 つのサブタスクがあり、これらは「bad account」シリーズを構成しています。この記事はシリーズの 3 番目のタスクである「Orderless Hashing」です。
分析#
この問題は前の問題と同様に 2 つの契約があり、アカウント契約「GrandPharaoh」のコンストラクタで特定の公開鍵が指定されています。__validate__
のget_multicall_hash
では、各 Call に対して再帰的にハッシュを計算し、最後に排他的論理和(^)を使用してハッシュを組み合わせます。ハッシュは u256 であり、low * high が最終的な messageHash です。私たちは、署名に渡す r と s を構築する必要があり、これらは ECDSA で検証できます。
実行する必要がある Calls は、別の契約の「RoyalSpear.equip (0)」であり、アカウント契約「GrandPharaoh」によってのみ呼び出すことが制限されています。
秘密鍵がないため、ECDSA の脆弱性を見つけて解読する必要があります。
手順#
まず、Calls リストの計算結果がどのようなものかを確認する必要があります。これは[equip(0)]
であるか、[equip(1),equip(0)]
またはそれ以上であるかは問題ではありません。最後の値が equip (0) である必要があります。
この部分は、cairo のテストを書くために snforge を使用し、アカウント契約に(multicall_hash.low.into() * multicall_hash.high.into()).print();
を追加して読み取ることができます。
明らかに、[equip(0),equip(0)]
に対応するハッシュは 0 であり、ここから始めることができます。
次に、check_ecdsa_signature に詳細があるかどうかを確認します。
use ecdsa::check_ecdsa_signature;
をコメントアウトし、ソースコードをテストに貼り付け、必要な場所に 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 を使用して、特定の署名とコールを渡してタスクを完了します。
結論#
ECDSA の学習を深め、楕円曲線の演算規則を理解していれば、この問題は簡単に解答できます。契約のマルチサイン設計は非常に単純であり、署名のリプレイ攻撃を利用することができます。
これで、アカウントの 3 つの問題がすべて解決しました。Cairo シリーズ全体には、Cairo で仮想マシンを書くものがありますが、私はアルゴリズム問題が嫌いです。将来的には、Starknet で実際に開発中に遭遇した興味深いトピックを更新する予定ですので、お楽しみに。