Coverage for tests/admin/test_signmigration.py: 100%
305 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.
23from unittest import TestCase
24from unittest.mock import Mock, call, patch, mock_open
25from signmigration import main
26from admin.bip32 import BIP32Path
27import ecdsa
28import logging
30logging.disable(logging.CRITICAL)
32RETURN_SUCCESS = 0
33RETURN_ERROR = 1
36@patch("signmigration.SGXMigrationAuthorization")
37@patch("signmigration.SGXMigrationSpec")
38@patch("signmigration.info")
39class TestSignMigrationMessage(TestCase):
40 def setUp(self):
41 self.migration_auth = Mock()
42 self.migration_spec = Mock()
43 self.migration_spec.get_authorization_msg.return_value = (
44 b"the-authorization-message"
45 )
46 self.migration_auth.for_spec.return_value = self.migration_auth
48 def test_ok_to_console(self, info_mock, migration_spec_mock, migration_auth_mock):
49 migration_spec_mock.return_value = self.migration_spec
50 migration_auth_mock.for_spec.return_value = self.migration_auth
52 with patch("sys.argv", ["signmigration.py", "message",
53 "-e", "exporter-hash",
54 "-i", "importer-hash"]):
55 with self.assertRaises(SystemExit) as exit:
56 main()
58 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
59 self.assertEqual(
60 [call("Computing the SGX migration authorization message..."),
61 call("the-authorization-message")], info_mock.call_args_list
62 )
63 self.assertEqual(
64 [call({"exporter": "exporter-hash", "importer": "importer-hash"})],
65 migration_spec_mock.call_args_list
66 )
67 self.assertEqual(
68 [call(self.migration_spec)],
69 migration_auth_mock.for_spec.call_args_list
70 )
72 def test_ok_to_file(self, info_mock, migration_spec_mock, migration_auth_mock):
73 migration_spec_mock.return_value = self.migration_spec
74 migration_auth_mock.for_spec.return_value = self.migration_auth
76 with patch("sys.argv", ["signmigration.py", "message",
77 "-e", "exporter-hash",
78 "-i", "importer-hash",
79 "-o", "an-output-path"]):
80 with self.assertRaises(SystemExit) as exit:
81 main()
83 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
84 self.assertEqual(
85 [call("Computing the SGX migration authorization message..."),
86 call("SGX migration authorization saved to an-output-path")],
87 info_mock.call_args_list
88 )
89 self.assertEqual(
90 [call({"exporter": "exporter-hash", "importer": "importer-hash"})],
91 migration_spec_mock.call_args_list
92 )
93 self.assertEqual(
94 [call(self.migration_spec)],
95 migration_auth_mock.for_spec.call_args_list
96 )
97 self.assertEqual(
98 [call("an-output-path")],
99 self.migration_auth.save_to_jsonfile.call_args_list
100 )
102 def test_missing_exporter(self, info_mock, migration_spec_mock, migration_auth_mock):
103 with patch("sys.argv", ["signmigration.py", "message",
104 "-i", "importer-hash"]):
105 with self.assertRaises(SystemExit) as exit:
106 main()
108 self.assertEqual(exit.exception.code, RETURN_ERROR)
109 self.assertEqual(
110 [call("Must provide an exporter hash (-e/--exporter)")],
111 info_mock.call_args_list
112 )
113 migration_spec_mock.assert_not_called()
114 migration_auth_mock.for_spec.assert_not_called()
116 def test_missing_importer(self, info_mock, migration_spec_mock, migration_auth_mock):
117 with patch("sys.argv", ["signmigration.py", "message",
118 "-e", "exporter-hash"]):
119 with self.assertRaises(SystemExit) as exit:
120 main()
122 self.assertEqual(exit.exception.code, RETURN_ERROR)
123 self.assertEqual(
124 [call("Must provide an importer hash (-i/--importer)")],
125 info_mock.call_args_list
126 )
127 migration_spec_mock.assert_not_called()
128 migration_auth_mock.for_spec.assert_not_called()
131@patch("signmigration.isfile")
132@patch("signmigration.SGXMigrationAuthorization")
133@patch("signmigration.info")
134class TestSignMigrationManual(TestCase):
135 def test_ok(self, info_mock, migration_auth_mock, isfile_mock):
136 migration_auth = Mock()
137 migration_auth_mock.from_jsonfile.return_value = migration_auth
138 isfile_mock.return_value = True
140 with patch("sys.argv", ["signmigration.py", "manual",
141 "-o", "an-output-path",
142 "-g", "a-signature"]):
143 with self.assertRaises(SystemExit) as exit:
144 main()
146 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
147 self.assertEqual(
148 [call("an-output-path")],
149 migration_auth_mock.from_jsonfile.call_args_list
150 )
151 self.assertEqual(
152 [call("a-signature")],
153 migration_auth.add_signature.call_args_list
154 )
155 self.assertEqual(
156 [call("an-output-path")],
157 migration_auth.save_to_jsonfile.call_args_list
158 )
159 self.assertEqual(
160 [
161 call("Opening SGX migration authorization file an-output-path..."),
162 call("Adding signature..."),
163 call("SGX migration authorization saved to an-output-path")
164 ],
165 info_mock.call_args_list
166 )
168 def test_file_not_found(self, info_mock, migration_auth_mock, isfile_mock):
169 isfile_mock.return_value = False
171 with patch("sys.argv", ["signmigration.py", "manual",
172 "-o", "an-output-path",
173 "-g", "a-signature"]):
174 with self.assertRaises(SystemExit) as exit:
175 main()
177 self.assertEqual(exit.exception.code, RETURN_ERROR)
178 self.assertEqual(
179 [
180 call("Invalid output path: an-output-path")
181 ],
182 info_mock.call_args_list
183 )
184 migration_auth_mock.from_jsonfile.assert_not_called()
186 def test_missing_signature(self, info_mock, migration_auth_mock, isfile_mock):
187 migration_auth = Mock()
188 migration_auth_mock.from_jsonfile.return_value = migration_auth
189 isfile_mock.return_value = True
191 with patch("sys.argv", ["signmigration.py", "manual",
192 "-o", "an-output-path"]):
193 with self.assertRaises(SystemExit) as exit:
194 main()
196 self.assertEqual(exit.exception.code, RETURN_ERROR)
197 self.assertEqual(
198 [
199 call("Must provide a signature (-g/--signature)"),
200 ],
201 info_mock.call_args_list
202 )
203 migration_auth_mock.from_jsonfile.assert_not_called()
204 migration_auth.add_signature.assert_not_called()
205 migration_auth.save_to_jsonfile.assert_not_called()
207 def test_missing_output_file(self, info_mock, migration_auth_mock, isfile_mock):
208 with patch("sys.argv", ["signmigration.py", "manual",
209 "-g", "a-signature"]):
210 with self.assertRaises(SystemExit) as exit:
211 main()
213 self.assertEqual(exit.exception.code, RETURN_ERROR)
214 self.assertEqual(
215 [
216 call("Must provide an output path (-o/--output)"),
217 ],
218 info_mock.call_args_list
219 )
220 isfile_mock.assert_not_called()
221 migration_auth_mock.from_jsonfile.assert_not_called()
222 migration_auth_mock.add_signature.assert_not_called()
223 migration_auth_mock.save_to_jsonfile.assert_not_called()
225 def test_non_existent_output_file(self, info_mock, migration_auth_mock, isfile_mock):
226 isfile_mock.return_value = False
228 with patch("sys.argv", ["signmigration.py", "manual",
229 "-o", "an-output-path"]):
230 with self.assertRaises(SystemExit) as exit:
231 main()
233 self.assertEqual(exit.exception.code, RETURN_ERROR)
234 isfile_mock.assert_called_once_with("an-output-path")
235 self.assertEqual(
236 [
237 call("Invalid output path: an-output-path"),
238 ],
239 info_mock.call_args_list
240 )
241 migration_auth_mock.from_jsonfile.assert_not_called()
242 migration_auth_mock.add_signature.assert_not_called()
243 migration_auth_mock.save_to_jsonfile.assert_not_called()
246@patch("signmigration.isfile")
247@patch("signmigration.SGXMigrationAuthorization")
248@patch("signmigration.info")
249class TestSignMigrationKey(TestCase):
250 def test_ok(self, info_mock, migration_auth_mock, isfile_mock):
251 migration_auth = Mock()
252 migration_auth_mock.from_jsonfile.return_value = migration_auth
253 migration_auth.add_signature.return_value = None
254 isfile_mock.return_value = True
255 migration_auth.migration_spec.get_authorization_digest.return_value = (
256 bytes.fromhex("bb"*32)
257 )
259 with patch("sys.argv", ["signmigration.py", "key",
260 "-o", "an-output-path",
261 "-k", "aa"*32]):
262 with self.assertRaises(SystemExit) as exit:
263 main()
265 privkey = ecdsa.SigningKey.from_string(
266 bytes.fromhex("aa"*32),
267 curve=ecdsa.SECP256k1
268 )
269 pubkey = privkey.get_verifying_key()
270 signature = migration_auth.add_signature.call_args_list[0][0][0]
271 pubkey.verify_digest(bytes.fromhex(signature), bytes.fromhex("bb"*32),
272 sigdecode=ecdsa.util.sigdecode_der)
273 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
274 self.assertEqual(
275 [call("an-output-path")],
276 migration_auth_mock.from_jsonfile.call_args_list
277 )
278 self.assertEqual(
279 [
280 call("Opening SGX migration authorization file an-output-path..."),
281 call("Signing with key..."),
282 call("SGX migration authorization saved to an-output-path")
283 ],
284 info_mock.call_args_list
285 )
287 def test_missing_key(self, info_mock, migration_auth_mock, isfile_mock):
288 isfile_mock.return_value = True
290 with patch("sys.argv", ["signmigration.py", "key",
291 "-o", "an-output-path"]):
292 with self.assertRaises(SystemExit) as exit:
293 main()
295 self.assertEqual(exit.exception.code, RETURN_ERROR)
296 self.assertEqual(
297 [
298 call("Must provide a signing key (-k/--key)"),
299 ],
300 info_mock.call_args_list
301 )
302 migration_auth_mock.from_jsonfile.assert_not_called()
303 migration_auth_mock.add_signature.assert_not_called()
304 migration_auth_mock.save_to_jsonfile.assert_not_called()
306 def test_invalid_key(self, info_mock, migration_auth_mock, isfile_mock):
307 isfile_mock.return_value = True
309 with patch("sys.argv", ["signmigration.py", "key",
310 "-o", "an-output-path",
311 "-k", "invalid-key"]):
312 with self.assertRaises(SystemExit) as exit:
313 main()
315 self.assertEqual(exit.exception.code, RETURN_ERROR)
316 self.assertEqual(
317 [
318 call("Invalid key 'invalid-key'"),
319 ],
320 info_mock.call_args_list
321 )
322 migration_auth_mock.from_jsonfile.assert_not_called()
323 migration_auth_mock.add_signature.assert_not_called()
324 migration_auth_mock.save_to_jsonfile.assert_not_called()
326 def test_missing_output_file(self, info_mock, migration_auth_mock, isfile_mock):
327 with patch("sys.argv", ["signmigration.py", "key",
328 "-k", "aa"*32]):
329 with self.assertRaises(SystemExit) as exit:
330 main()
332 self.assertEqual(exit.exception.code, RETURN_ERROR)
333 self.assertEqual(
334 [
335 call("Must provide an output path (-o/--output)"),
336 ],
337 info_mock.call_args_list
338 )
339 isfile_mock.assert_not_called()
340 migration_auth_mock.from_jsonfile.assert_not_called()
342 def test_non_existent_output_file(self, info_mock, migration_auth_mock, isfile_mock):
343 isfile_mock.return_value = False
345 with patch("sys.argv", ["signmigration.py", "key",
346 "-o", "an-output-path",
347 "-k", "aa"*32]):
348 with self.assertRaises(SystemExit) as exit:
349 main()
351 self.assertEqual(exit.exception.code, RETURN_ERROR)
352 isfile_mock.assert_called_once_with("an-output-path")
353 self.assertEqual(
354 [
355 call("Invalid output path: an-output-path"),
356 ],
357 info_mock.call_args_list
358 )
359 migration_auth_mock.from_jsonfile.assert_not_called()
361 def test_canonical_signature_encoding(self, _, migration_auth_mock, isfile_mock):
362 migration_auth = Mock()
363 migration_auth_mock.from_jsonfile.return_value = migration_auth
364 migration_auth.add_signature.return_value = None
365 isfile_mock.return_value = True
366 test_digest = bytes.fromhex("bb"*32)
367 migration_auth.migration_spec.get_authorization_digest.return_value = test_digest
369 with patch("signmigration.ecdsa.util.sigencode_der_canonize") as sigencode_mock:
370 known_signature = bytes.fromhex(
371 "30440220" + "11" * 32 + "0220" + "22" * 32
372 )
373 sigencode_mock.return_value = known_signature
375 with patch("sys.argv", ["signmigration.py", "key",
376 "-o", "an-output-path",
377 "-k", "aa"*32]):
378 with self.assertRaises(SystemExit) as exit:
379 main()
381 self.assertEqual(sigencode_mock.call_count, 1)
382 signature_hex = migration_auth.add_signature.call_args_list[0][0][0]
383 self.assertEqual(signature_hex, known_signature.hex())
385 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
388@patch("signmigration.isfile")
389@patch("signmigration.dispose_eth_dongle")
390@patch("signmigration.get_eth_dongle")
391@patch("signmigration.BIP32Path")
392@patch("signmigration.SGXMigrationSpec")
393@patch("signmigration.SGXMigrationAuthorization")
394@patch("signmigration.info")
395class TestSignMigrationEth(TestCase):
396 def test_ok_pubkey(
397 self,
398 info_mock,
399 migration_auth_mock,
400 migration_spec_mock,
401 bip32path_mock,
402 get_eth_mock,
403 dispose_eth_mock,
404 isfile_mock):
405 migration_auth = Mock()
406 migration_auth_mock.from_jsonfile.return_value = migration_auth
407 bip32path_mock.return_value = "bip32-path"
408 get_eth_mock.return_value = Mock()
409 eth_mock = Mock()
410 eth_mock.get_pubkey.return_value = bytes.fromhex("aa"*32)
411 eth_mock.sign.return_value = bytes.fromhex("bb"*32)
412 get_eth_mock.return_value = eth_mock
413 isfile_mock.return_value = True
415 mock_file = mock_open()
416 with patch("builtins.open", mock_file) as open_mock:
417 with patch("sys.argv", ["signmigration.py", "eth",
418 "-o", "an-output-path", "-b"]):
419 with self.assertRaises(SystemExit) as exit:
420 main()
422 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
423 get_eth_mock.assert_called_once()
424 eth_mock.get_pubkey.assert_called_once_with("bip32-path")
425 self.assertEqual(
426 [
427 call("Retrieving public key for path 'bip32-path'..."),
428 call("Public key: " + "aa"*32),
429 call("Opening public key file an-output-path..."),
430 call("Adding public key..."),
431 call("Public key saved to an-output-path")
432 ],
433 info_mock.call_args_list
434 )
435 # Verify the file was opened in write mode
436 open_mock.assert_called_once_with("an-output-path", "w")
437 # Verify the correct content was written
438 mock_file.return_value.write.assert_called_once_with("aa"*32 + "\n")
440 def test_existingfile_ok(
441 self,
442 info_mock,
443 migration_auth_mock,
444 migration_spec_mock,
445 bip32path_mock,
446 get_eth_mock,
447 dispose_eth_mock,
448 isfile_mock):
449 migration_spec_mock = Mock()
450 migration_spec_mock.get_authorization_digest.return_value = bytes.fromhex("aa"*32)
451 migration_spec_mock.msg = "RSK_powHSM_SGX_upgrade_from_exporter_to_importer"
452 migration_auth = Mock()
453 migration_auth.migration_spec = migration_spec_mock
454 migration_auth_mock.from_jsonfile.return_value = migration_auth
455 bip32path_mock.return_value = BIP32Path("m/44'/60'/0'/0/0")
456 privkey = ecdsa.SigningKey.from_string(bytes.fromhex("dd"*32),
457 curve=ecdsa.SECP256k1)
458 pubkey = privkey.get_verifying_key()
459 eth_mock = Mock()
460 eth_mock.get_pubkey.return_value = pubkey.to_string("uncompressed")
461 eth_mock.sign.return_value = privkey.sign_digest(
462 bytes.fromhex("aa"*32), sigencode=ecdsa.util.sigencode_der)
463 get_eth_mock.return_value = eth_mock
464 isfile_mock.return_value = True
466 with patch("sys.argv", ["signmigration.py", "eth",
467 "-o", "an-output-path"]):
468 with self.assertRaises(SystemExit) as exit:
469 main()
471 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
473 self.assertEqual([call("an-output-path")], isfile_mock.call_args_list)
474 self.assertFalse(migration_spec_mock.called)
475 self.assertEqual([call("an-output-path")],
476 migration_auth_mock.from_jsonfile.call_args_list)
477 self.assertEqual([call(BIP32Path("m/44'/60'/0'/0/0"))],
478 eth_mock.get_pubkey.call_args_list)
479 self.assertEqual([call(BIP32Path("m/44'/60'/0'/0/0"),
480 b"RSK_powHSM_SGX_upgrade_from_exporter_to_importer")],
481 eth_mock.sign.call_args_list)
482 self.assertEqual(1, migration_auth.add_signature.call_count)
483 signature = migration_auth.add_signature.call_args_list[0][0][0]
484 pubkey.verify_digest(bytes.fromhex(signature), bytes.fromhex("aa"*32),
485 sigdecode=ecdsa.util.sigdecode_der)
486 self.assertEqual([call("an-output-path")],
487 migration_auth.save_to_jsonfile.call_args_list)
489 def test_missing_output_file(
490 self,
491 info_mock,
492 migration_auth_mock,
493 migration_spec_mock,
494 bip32path_mock,
495 get_eth_mock,
496 dispose_eth_mock,
497 isfile_mock):
499 with patch("sys.argv", ["signmigration.py", "eth"]):
500 with self.assertRaises(SystemExit) as exit:
501 main()
503 self.assertEqual(exit.exception.code, RETURN_ERROR)
504 self.assertEqual(
505 [call("Must provide an output path (-o/--output)")],
506 info_mock.call_args_list
507 )
508 get_eth_mock.assert_not_called()
509 dispose_eth_mock.assert_not_called()
511 def test_get_eth_dongle_exception(
512 self,
513 info_mock,
514 migration_auth_mock,
515 migration_spec_mock,
516 bip32path_mock,
517 get_eth_mock,
518 dispose_eth_mock,
519 isfile_mock):
521 bip32path_mock.return_value = BIP32Path("m/44'/60'/0'/0/0")
522 get_eth_mock.side_effect = Exception("Dongle connection error")
524 with patch("sys.argv", ["signmigration.py", "eth",
525 "-o", "an-output-path"]):
526 with self.assertRaises(SystemExit) as exit:
527 main()
529 self.assertEqual(exit.exception.code, RETURN_ERROR)
530 bip32path_mock.assert_called_once_with("m/44'/60'/0'/0/0")
531 get_eth_mock.assert_called_once()
532 self.assertEqual(
533 [call("Error signing with dongle: Dongle connection error")],
534 info_mock.call_args_list
535 )
536 # dispose_eth_dongle should be called even if get_eth_dongle fails
537 dispose_eth_mock.assert_called_once_with(None)
539 def test_get_pubkey_exception(
540 self,
541 info_mock,
542 migration_auth_mock,
543 migration_spec_mock,
544 bip32path_mock,
545 get_eth_mock,
546 dispose_eth_mock,
547 isfile_mock):
549 bip32path_mock.return_value = BIP32Path("m/44'/60'/0'/0/0")
550 eth_mock = Mock()
551 eth_mock.get_pubkey.side_effect = Exception("Could not get pubkey")
552 get_eth_mock.return_value = eth_mock
554 with patch("sys.argv", ["signmigration.py", "eth",
555 "-o", "an-output-path"]):
556 with self.assertRaises(SystemExit) as exit:
557 main()
559 self.assertEqual(exit.exception.code, RETURN_ERROR)
560 bip32path_mock.assert_called_once_with("m/44'/60'/0'/0/0")
561 get_eth_mock.assert_called_once()
562 eth_mock.get_pubkey.assert_called_once_with(BIP32Path("m/44'/60'/0'/0/0"))
563 self.assertEqual(
564 [
565 call("Retrieving public key for path 'm/44\'/60\'/0\'/0/0'..."),
566 call("Error signing with dongle: Could not get pubkey")
567 ],
568 info_mock.call_args_list
569 )
570 dispose_eth_mock.assert_called_once_with(eth_mock)
572 def test_bad_signature(
573 self,
574 info_mock,
575 migration_auth_mock,
576 migration_spec_mock,
577 bip32path_mock,
578 get_eth_mock,
579 dispose_eth_mock,
580 isfile_mock):
582 migration_spec = Mock()
583 migration_spec.get_authorization_digest.return_value = bytes.fromhex("aa"*32)
584 migration_spec.msg = "RSK_powHSM_SGX_upgrade_from_exporter_to_importer"
585 migration_auth = Mock()
586 migration_auth.migration_spec = migration_spec
587 migration_auth_mock.from_jsonfile.return_value = migration_auth
588 bip32path_mock.return_value = BIP32Path("m/44'/60'/0'/0/0")
590 # Generate a valid key pair
591 privkey = ecdsa.SigningKey.from_string(bytes.fromhex("dd"*32),
592 curve=ecdsa.SECP256k1)
593 pubkey = privkey.get_verifying_key()
595 eth_mock = Mock()
596 eth_mock.get_pubkey.return_value = pubkey.to_string("uncompressed")
597 # Sign a DIFFERENT digest to create a bad signature for the expected digest
598 bad_signature = privkey.sign_digest(
599 bytes.fromhex("cc"*32), sigencode=ecdsa.util.sigencode_der)
600 eth_mock.sign.return_value = bad_signature
601 get_eth_mock.return_value = eth_mock
602 isfile_mock.return_value = True
604 with patch("sys.argv", ["signmigration.py", "eth",
605 "-o", "an-output-path"]):
606 with self.assertRaises(SystemExit) as exit:
607 main()
609 self.assertEqual(exit.exception.code, RETURN_ERROR)
610 isfile_mock.assert_called_once_with("an-output-path")
611 migration_auth_mock.from_jsonfile.assert_called_once_with("an-output-path")
612 get_eth_mock.assert_called_once()
613 eth_mock.get_pubkey.assert_called_once_with(BIP32Path("m/44'/60'/0'/0/0"))
614 eth_mock.sign.assert_called_once_with(
615 BIP32Path("m/44'/60'/0'/0/0"),
616 b"RSK_powHSM_SGX_upgrade_from_exporter_to_importer"
617 )
618 self.assertEqual(
619 [
620 call("Retrieving public key for path 'm/44\'/60\'/0\'/0/0'..."),
621 call(f"Public key: {pubkey.to_string('uncompressed').hex()}"),
622 call("Opening SGX migration authorization file an-output-path..."),
623 call("Signing with dongle..."),
624 call(f"Bad signature from dongle! (got '{bad_signature.hex()}')")
625 ],
626 info_mock.call_args_list
627 )
628 migration_auth.add_signature.assert_not_called()
629 migration_auth.save_to_jsonfile.assert_not_called()
630 dispose_eth_mock.assert_called_once_with(eth_mock)