xChar

#NG9

关于合约账户有3个子任务,共同组成了 bad account 系列。本文是系列的第二个任务 Bendy Signatures。

分析

题目里有2个合约,sphinx 是抽象账户合约,由账户去调用 gates 合约的 open 方法。sphinx 有很多限制,给了3个公钥,其中第1个有私钥。题目模拟的是2/3多签问题,需要传入2组签名都认证成功才能发送验证交易。

在账户合约的__validate__中使用了starknet::get_tx_info().unbox()拿到了交易的签名,需要我们构造长度为6的 raw_sig,其中 raw_sig[0] 和 raw_sig[3] 是公钥,raw_sig[1] 和 raw_sig[4] 是签名的 r,raw_sig[2] 和 raw_sig[5] 是 签名的 s。需要公钥是预设的3个公钥之一,且 r 和 s 不能重复。同时 __execute__ 中限制了calls.len() == 1,所以不能用唯一的私钥生成2笔交易去解题。

由于只有1个私钥,需要用这个私钥签名生成两组 r 和 s 都能通过验证且不重复。问题变成了如何用SDK,发送一笔交易,但是有 2 组有效签名。

过程

我首先尝试在 Starknet.js 中用2个不同的 maxFee 生成了两组 open 交易并分别签名,发现无法被测试网验证。

询问导师后,发现这题实际要破解的问题是 ECDSA malleability 问题,具体可参考ZK book

我花了一段时间学习 ECDSA,其中涉及了很多数学知识。
如果你不知道 ECDSA 原理,建议也进行深入学习。上面的 ZK Book 内容很棒。

StarkNet 也是使用了 ECDSA 来生成签名,ChatGPT输出的ECDSA签名流程是:

1. 首先,需要一个私钥,这是一个随机选择的整数。同时,还需要一个公钥,这是私钥和基点G的乘积,记作 Q = dG,其中 d 是私钥,G 是基点。
2. 当你要签名一个消息时,首先将消息通过哈希函数转化为一个整数,记作 z。
3. 然后,选择一个随机整数 k,并计算点 R = kG。R 的x坐标就是签名的一部分,记作 r。
4. 接着,计算 s = (z + r * d) / k。这个 s 就是签名的另一部分。
最终签名就是 (r, s)。

对应每一步在Starknet.js中是:

  1. ec.starkCurve.getStarkKey(privateKey)获取公钥,公钥是已知的,可以跳过这步
  2. hash.calculateTransactionHash把交易详情转换为一个messageHash。交易需要的6个参数需要自己设置,其中calldata用 transaction.getExecuteCalldata获得
  3. r是一个随机数,不用自己设置,下一步可直接获取
  4. 通过 ec.starkCurve.sign(msgHash, privateKey) 或者 signer.signTransaction来得到r和s。

通过上面 ECDSA malleability 的参考资料可知由于对称性,1 个 r 对应了 2 个 s 都可以使签名有效。第二个 s 的计算很简单,所需的另一个参数可在 SDK 的源码中找到。最终生成的 r1 == r2, s1 != s2。

另外一个方法是用不同的 seed 生成两个 r,python库 里有对应的方法。最终生成的 r1 != r2, s1 != s2。

按题目要求构造一个长度为6的列表,传入 account.invokeFunction 的signature中,填上其他所需参数,发送即可完成任务。

总结

NG9 end

这题一方面介绍了多签钱包合约是如何工作的,另一方面涉及 ECDSA malleability 问题,还需要了解 transaction 的构成,如果能完成会很有收获。

Loading comments...