14.Public Key
2023-06-23 20:22:15 # 01.Capturetheether CTF

Public Key

topic

1
2
3
4
5
6
7
8
9
10
11
12
pragma solidity ^0.4.21;

contract PublicKeyChallenge {
address owner = 0x92b28647ae1f3264661f72fb2eb9625a89d88a31;
bool public isComplete;

function authenticate(bytes publicKey) public {
require(address(keccak256(publicKey)) == owner);

isComplete = true;
}
}

analyses

这道题是要我们找到地址owner的公钥,owner本题默认是合约部署人的地址。意思就是说,我们要找到合约部署人的公钥。值得注意的是,metamask只能获得私钥和地址,无法直接获取公钥

以下是私钥、公钥、地址之间的关系:

1
2
3
Public Key = ECDSA(Private Key)
A = Keccak-256(Public Key)
Address = '0x' + last 20 bytes of A

回到本题,需要寻找公钥。因为在以太坊交易中,每一笔交易transaction hash可以解析出vrs,进而可以用ECDSA算法还原出公钥。因此,我们找到部署此合约的那笔交易transaction hash,然后推导出公钥即可

因为此靶场的测试网已经弃用了,因此我想在goerli测试网还原此题目,题目修改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
pragma solidity ^0.4.21;

contract PublicKeyChallenge {
address owner = 0xd3E65149C212902749D49011B6ab24bba30D97c6;
bool public isComplete;

function authenticate(bytes publicKey) public {
require(address(keccak256(publicKey)) == owner);

isComplete = true;
}
}

solution

1.部署题目:transaction hash = 0x60605c7e3fddd2be1fa63f4fa8ef12bfc5e5c69062c6a4788d1277e007e7de02

2.想要根据transaction hash还原出公钥,可以用这个工具代码(别人造的轮子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def GetPublicKeyByTransactionHash(self, TransactionHash: str) -> tuple:
"""
通过一笔已在链上确认的交易哈希,获取账户公钥。

参数:
TransactionHash (str): 交易哈希

返回值:
(Address, PublicKey) (tuple): 由账户地址和账户公钥组成的元组。当出现异常时返回 None 。
"""

try:
from eth_account._utils.signing import to_standard_v, extract_chain_id, serializable_unsigned_transaction_from_dict

Transaction = self.Eth.get_transaction(TransactionHash)
Signature = self.Eth.account._keys.Signature(vrs=(to_standard_v(extract_chain_id(Transaction.v)[1]), Web3.to_int(Transaction.r), Web3.to_int(Transaction.s)))
UnsignedTransactionDict = {i: Transaction[i] for i in ['chainId', 'nonce', 'gasPrice' if Transaction.type != 2 else '', 'gas', 'to', 'value', 'accessList', 'maxFeePerGas', 'maxPriorityFeePerGas'] if i in Transaction}
UnsignedTransactionDict['data'] = Transaction['input']
UnsignedTransaction = serializable_unsigned_transaction_from_dict(UnsignedTransactionDict)
Temp = Signature.recover_public_key_from_msg_hash(UnsignedTransaction.hash())
#PublicKey = str(Temp).replace('0x', '0x04') # 比特币未压缩公钥格式
PublicKey = str(Temp)
Address = Temp.to_checksum_address()
logger.success(
f"\n[Chain][GetPublicKeyByTransactionHash]\n[TransactionHash] {TransactionHash}\n[Address] {Address}\n[PublicKey] {PublicKey}\n{'-'*80}"
)
return (Address, PublicKey)
except Exception:
ExceptionInformation = format_exc()
logger.error(
f"\n[Chain][GetPublicKeyByTransactionHash]Failed\n[TransactionHash]{TransactionHash}\n[ExceptionInformation]{ExceptionInformation}{'-'*80}"
)
return None

3.然后还原出我的公钥:0x57bffa5cd5803fb0f50dc86d9b1c51c08d9679a42fbc2e3f6c6d0f0d24150989103adbe5f8c032597501a8aa33837ea74dda3571cf4ced21eb9336308a98988d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2023-03-21 22:51:47.309 | SUCCESS  | __main__:__init__:35 - 
[Chain][Initialize]Connected to [https://goerli.infura.io/v3/zzzzzzzzzzzzzz] [1518 ms]
--------------------------------------------------------------------------------
2023-03-21 22:51:49.651 | SUCCESS | __main__:GetBasicInformation:57 -
[Chain][GetBasicInformation]
[ChainId]5
[BlockNumber]8693699
[GasPrice]216.92452905 Gwei
[Timeslot]12s
[ClientVersion]Geth/v1.11.4-omnibus-8c089133/linux-amd64/go1.19.7
--------------------------------------------------------------------------------
2023-03-21 22:51:50.053 | SUCCESS | __main__:GetPublicKeyByTransactionHash:87 -
[Chain][GetPublicKeyByTransactionHash]
[TransactionHash]0x60605c7e3fddd2be1fa63f4fa8ef12bfc5e5c69062c6a4788d1277e007e7de02
[Address]0xd3E65149C212902749D49011B6ab24bba30D97c6
[PublicKey]0x57bffa5cd5803fb0f50dc86d9b1c51c08d9679a42fbc2e3f6c6d0f0d24150989103adbe5f8c032597501a8aa33837ea74dda3571cf4ced21eb9336308a98988d
--------------------------------------------------------------------------------

4.调用authenticate()即可完成本题