script (⏱,💰)

script (⏱,💰)

NG#10 - 无序多签账户

NG10

关于合约账户有 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 完成任务。

总结#

NG10 end

如果深入学习了 ECDSA,知道椭圆曲线的运算规则,这题是很容易找到答案的。合约的多签设计太简单,可以利用签名重放进行攻击。

就此 Account 部分的三题就全部完成了。整个 Cairo 系列还有一题用 Cairo 写虚拟机的不打算做了,我讨厌算法题。未来会更新在 Starknet 实际开发中遇到的有趣的东西,敬请期待。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。