Coverage for admin/pubkeys.py: 97%
66 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2021 RSK Labs Ltd
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy of
6# this software and associated documentation files (the "Software"), to deal in
7# the Software without restriction, including without limitation the rights to
8# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9# of the Software, and to permit persons to whom the Software is furnished to do
10# so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in all
13# copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21# SOFTWARE.
23import os
24import json
25import ecdsa
26from ledger.hsm2dongle import HSM2Dongle
27from .misc import info, get_hsm, dispose_hsm, AdminError, wait_for_reconnection
28from .unlock import do_unlock
29from comm.bip32 import BIP32Path
30from comm.platform import Platform
32SIGNER_WAIT_TIME = 1 # second
34PATHS = {
35 "btc": BIP32Path("m/44'/0'/0'/0/0"),
36 "rsk": BIP32Path("m/44'/137'/0'/0/0"),
37 "mst": BIP32Path("m/44'/137'/1'/0/0"),
38 "tbtc": BIP32Path("m/44'/1'/0'/0/0"),
39 "trsk": BIP32Path("m/44'/1'/1'/0/0"),
40 "tmst": BIP32Path("m/44'/1'/2'/0/0"),
41}
44def do_get_pubkeys(options):
45 info("### -> Get public keys")
46 hsm = None
48 # Attempt to unlock and open the signing app
49 if not options.no_unlock:
50 try:
51 do_unlock(options, label=False)
52 except Exception as e:
53 raise AdminError(f"Failed to unlock device: {str(e)}")
55 # Wait for the signer
56 wait_for_reconnection()
58 # Connection
59 hsm = get_hsm(options.verbose)
61 # Mode check
62 info("Finding mode... ", options.verbose)
63 mode = hsm.get_current_mode()
64 info(f"Mode: {mode.name.capitalize()}")
66 # Modes for which we can't get the public keys
67 if mode in [HSM2Dongle.MODE.UNKNOWN, HSM2Dongle.MODE.BOOTLOADER]:
68 raise AdminError(
69 f"Device not in app mode. {Platform.message('restart').capitalize()}")
71 # Gather public keys
72 pubkeys = {}
73 for path_name in PATHS:
74 info(f"Getting public key for path '{path_name}'... ", options.verbose)
75 path = PATHS[path_name]
76 pubkeys[path_name] = hsm.get_public_key(path)
77 info("OK")
79 try:
80 output_file = None
81 save_to_json = False
82 if options.output_file_path is not None:
83 output_file = open(options.output_file_path, "w")
85 def do_output(s):
86 return output_file.write(f"{s}\n")
88 save_to_json = True
89 json_dict = {}
90 else:
92 def do_output(s):
93 return info(s)
95 do_output("*" * 80)
96 do_output("Name \t\t\t Path \t\t\t\t Pubkey")
97 do_output("==== \t\t\t ==== \t\t\t\t ======")
98 path_name_padding = max(map(len, PATHS))
99 for path_name in PATHS:
100 path = PATHS[path_name]
101 # Compress public key
102 pk = ecdsa.VerifyingKey.from_string(bytes.fromhex(pubkeys[path_name]),
103 curve=ecdsa.SECP256k1)
104 pubkey = pk.to_string("compressed").hex()
105 do_output(f"{path_name.ljust(path_name_padding)} \t\t {path} \t\t {pubkey}")
106 if save_to_json:
107 json_dict[str(path)] = pk.to_string("uncompressed").hex()
108 do_output("*" * 80)
110 if output_file is not None:
111 output_file.close()
112 info(f"Public keys saved to {options.output_file_path}")
114 if save_to_json:
115 json_output_file_path = (os.path.splitext(options.output_file_path)[0] +
116 ".json")
117 output_file = open(json_output_file_path, "w")
118 output_file.write("%s\n" % json.dumps(json_dict, indent=2))
119 output_file.close()
120 info(f"JSON-formatted public keys saved to {json_output_file_path}")
121 except Exception as e:
122 raise AdminError(f"Error writing output: {str(e)}")
124 dispose_hsm(hsm)