script (⏱,💰)

script (⏱,💰)

NG#9 - 多签账户

#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 的构成,如果能完成会很有收获。

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