Coverage for tests/admin/test_signapp.py: 100%
171 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
25from signapp import main
26from admin.bip32 import BIP32Path
27import ecdsa
28import logging
30logging.disable(logging.CRITICAL)
32RETURN_SUCCESS = 0
33RETURN_ERROR = 1
36@patch("signapp.compute_app_hash")
37@patch("signapp.info")
38class TestSignAppHash(TestCase):
39 def test_ok(self, info_mock, compute_app_hash_mock):
40 compute_app_hash_mock.return_value = bytes.fromhex("aabbcc")
42 with patch("sys.argv", ["signapp.py", "hash", "-a", "a-path"]):
43 with self.assertRaises(SystemExit) as exit:
44 main()
46 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
47 self.assertEqual(
48 [call("Computing hash..."),
49 call("Computed hash: aabbcc")], info_mock.call_args_list)
50 self.assertEqual([call("a-path")], compute_app_hash_mock.call_args_list)
53@patch("signapp.SignerAuthorization")
54@patch("signapp.SignerVersion")
55@patch("signapp.compute_app_hash")
56@patch("signapp.info")
57class TestSignAppMessage(TestCase):
58 def test_ok_to_console(self, info_mock, compute_app_hash_mock,
59 signer_version_mock, signer_authorization_mock):
60 compute_app_hash_mock.return_value = bytes.fromhex("aabbcc")
61 signer_version = Mock()
62 signer_version_mock.return_value = signer_version
63 signer_version.get_authorization_msg.return_value = b"the-authorization-message"
65 with patch("sys.argv", ["signapp.py", "message", "-a", "a-path",
66 "-i", "an-iteration"]):
67 with self.assertRaises(SystemExit) as exit:
68 main()
70 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
71 self.assertEqual(
72 [call("Computing hash..."),
73 call("Computing signer authorization message..."),
74 call("the-authorization-message")], info_mock.call_args_list)
76 self.assertEqual([call("aabbcc", "an-iteration")],
77 signer_version_mock.call_args_list)
79 def test_ok_to_file(self, info_mock, compute_app_hash_mock,
80 signer_version_mock, signer_authorization_mock):
81 compute_app_hash_mock.return_value = bytes.fromhex("aabbcc")
82 signer_version = Mock()
83 signer_version_mock.return_value = signer_version
84 signer_version.get_authorization_msg.return_value = b"the-authorization-message"
85 signer_authorization = Mock()
86 signer_authorization_mock.for_signer_version.return_value = signer_authorization
88 with patch("sys.argv", ["signapp.py", "message", "-a", "a-path",
89 "-i", "an-iteration", "-o", "an-output-path"]):
90 with self.assertRaises(SystemExit) as exit:
91 main()
93 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
95 self.assertEqual([call("aabbcc", "an-iteration")],
96 signer_version_mock.call_args_list)
97 self.assertEqual([call(signer_version)],
98 signer_authorization_mock.for_signer_version.call_args_list)
99 self.assertEqual([call("an-output-path")],
100 signer_authorization.save_to_jsonfile.call_args_list)
103@patch("signapp.isfile")
104@patch("signapp.SignerAuthorization")
105@patch("signapp.SignerVersion")
106@patch("signapp.compute_app_hash")
107@patch("signapp.info")
108class TestSignAppKey(TestCase):
109 def test_newfile_ok(self, info_mock, compute_app_hash_mock,
110 signer_version_mock, signer_authorization_mock,
111 isfile_mock):
112 compute_app_hash_mock.return_value = bytes.fromhex("aabbcc")
113 signer_version = Mock()
114 signer_version_mock.return_value = signer_version
115 signer_version.get_authorization_digest.return_value = bytes.fromhex("bb"*32)
116 signer_authorization = Mock()
117 signer_authorization_mock.for_signer_version.return_value = signer_authorization
118 isfile_mock.return_value = False
120 with patch("sys.argv", ["signapp.py", "key", "-a", "a-path",
121 "-i", "an-iteration", "-o", "an-output-path",
122 "-k", "aa"*32]):
123 with self.assertRaises(SystemExit) as exit:
124 main()
126 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
128 self.assertEqual([call("an-output-path")], isfile_mock.call_args_list)
129 self.assertEqual([call("aabbcc", "an-iteration")],
130 signer_version_mock.call_args_list)
131 self.assertEqual([call(signer_version)],
132 signer_authorization_mock.for_signer_version.call_args_list)
133 self.assertEqual(1, signer_authorization.add_signature.call_count)
134 signature = signer_authorization.add_signature.call_args_list[0][0][0]
135 pk = ecdsa.SigningKey\
136 .from_string(bytes.fromhex("aa"*32), curve=ecdsa.SECP256k1)\
137 .get_verifying_key()
138 pk.verify_digest(bytes.fromhex(signature), bytes.fromhex("bb"*32),
139 sigdecode=ecdsa.util.sigdecode_der)
140 self.assertEqual([call("an-output-path")],
141 signer_authorization.save_to_jsonfile.call_args_list)
143 def test_existingfile_ok(self, info_mock, compute_app_hash_mock,
144 signer_version_mock, signer_authorization_mock,
145 isfile_mock):
146 signer_version = Mock()
147 signer_version.get_authorization_digest.return_value = bytes.fromhex("bb"*32)
148 signer_authorization = Mock()
149 signer_authorization.signer_version = signer_version
150 signer_authorization_mock.from_jsonfile.return_value = signer_authorization
151 isfile_mock.return_value = True
153 with patch("sys.argv", ["signapp.py", "key",
154 "-o", "an-output-path",
155 "-k", "aa"*32]):
156 with self.assertRaises(SystemExit) as exit:
157 main()
159 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
161 self.assertEqual([call("an-output-path")], isfile_mock.call_args_list)
162 self.assertFalse(signer_version_mock.called)
163 self.assertEqual([call("an-output-path")],
164 signer_authorization_mock.from_jsonfile.call_args_list)
165 self.assertEqual(1, signer_authorization.add_signature.call_count)
166 signature = signer_authorization.add_signature.call_args_list[0][0][0]
167 pk = ecdsa.SigningKey\
168 .from_string(bytes.fromhex("aa"*32), curve=ecdsa.SECP256k1)\
169 .get_verifying_key()
170 pk.verify_digest(bytes.fromhex(signature), bytes.fromhex("bb"*32),
171 sigdecode=ecdsa.util.sigdecode_der)
172 self.assertEqual([call("an-output-path")],
173 signer_authorization.save_to_jsonfile.call_args_list)
176@patch("signapp.dispose_eth_dongle")
177@patch("signapp.get_eth_dongle")
178@patch("signapp.isfile")
179@patch("signapp.SignerAuthorization")
180@patch("signapp.SignerVersion")
181@patch("signapp.compute_app_hash")
182@patch("signapp.info")
183class TestSignAppEth(TestCase):
184 def test_newfile_ok(self, info_mock, compute_app_hash_mock,
185 signer_version_mock, signer_authorization_mock, isfile_mock,
186 get_eth_mock, dispose_eth_mock):
187 compute_app_hash_mock.return_value = bytes.fromhex("aabbcc")
188 signer_version = Mock()
189 signer_version_mock.return_value = signer_version
190 signer_version.get_authorization_digest.return_value = bytes.fromhex("bb"*32)
191 signer_version.msg = "RSK_powHSM_signer_aabbcc_iteration_an-iteration"
192 signer_authorization = Mock()
193 signer_authorization_mock.for_signer_version.return_value = signer_authorization
194 privkey = ecdsa.SigningKey.from_string(bytes.fromhex("aa"*32),
195 curve=ecdsa.SECP256k1)
196 pubkey = privkey.get_verifying_key()
197 eth_mock = Mock()
198 eth_mock.get_pubkey.return_value = pubkey.to_string("uncompressed")
199 eth_mock.sign.return_value = privkey.sign_digest(
200 bytes.fromhex("bb"*32), sigencode=ecdsa.util.sigencode_der)
201 get_eth_mock.return_value = eth_mock
202 isfile_mock.return_value = False
203 with patch("sys.argv", ["signapp.py", "eth", "-a", "a-path",
204 "-i", "an-iteration", "-o", "an-output-path"]):
205 with self.assertRaises(SystemExit) as exit:
206 main()
208 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
209 self.assertEqual([call("an-output-path")], isfile_mock.call_args_list)
210 self.assertEqual([call("aabbcc", "an-iteration")],
211 signer_version_mock.call_args_list)
212 self.assertEqual([call(signer_version)],
213 signer_authorization_mock.for_signer_version.call_args_list)
214 self.assertEqual([call(BIP32Path("m/44'/60'/0'/0/0"))],
215 eth_mock.get_pubkey.call_args_list)
216 self.assertEqual([call(BIP32Path("m/44'/60'/0'/0/0"),
217 b"RSK_powHSM_signer_aabbcc_iteration_an-iteration")],
218 eth_mock.sign.call_args_list)
219 self.assertEqual(1, signer_authorization.add_signature.call_count)
220 signature = signer_authorization.add_signature.call_args_list[0][0][0]
221 pubkey.verify_digest(bytes.fromhex(signature), bytes.fromhex("bb"*32),
222 sigdecode=ecdsa.util.sigdecode_der)
223 self.assertEqual([call("an-output-path")],
224 signer_authorization.save_to_jsonfile.call_args_list)
226 def test_existingfile_ok(self, info_mock, compute_app_hash_mock,
227 signer_version_mock, signer_authorization_mock, isfile_mock,
228 get_eth_mock, dispose_eth_mock):
229 signer_version = Mock()
230 signer_version.get_authorization_digest.return_value = bytes.fromhex("bb"*32)
231 signer_version.msg = "RSK_powHSM_signer_aabbcc_iteration_an-iteration"
232 signer_authorization = Mock()
233 signer_authorization.signer_version = signer_version
234 signer_authorization_mock.from_jsonfile.return_value = signer_authorization
235 privkey = ecdsa.SigningKey.from_string(bytes.fromhex("aa"*32),
236 curve=ecdsa.SECP256k1)
237 pubkey = privkey.get_verifying_key()
238 eth_mock = Mock()
239 eth_mock.get_pubkey.return_value = pubkey.to_string("uncompressed")
240 eth_mock.sign.return_value = privkey.sign_digest(
241 bytes.fromhex("bb"*32), sigencode=ecdsa.util.sigencode_der)
242 get_eth_mock.return_value = eth_mock
243 isfile_mock.return_value = True
245 with patch("sys.argv", ["signapp.py", "eth",
246 "-o", "an-output-path"]):
247 with self.assertRaises(SystemExit) as exit:
248 main()
250 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
252 self.assertEqual([call("an-output-path")], isfile_mock.call_args_list)
253 self.assertFalse(signer_version_mock.called)
254 self.assertEqual([call("an-output-path")],
255 signer_authorization_mock.from_jsonfile.call_args_list)
256 self.assertEqual([call(BIP32Path("m/44'/60'/0'/0/0"))],
257 eth_mock.get_pubkey.call_args_list)
258 self.assertEqual([call(BIP32Path("m/44'/60'/0'/0/0"),
259 b"RSK_powHSM_signer_aabbcc_iteration_an-iteration")],
260 eth_mock.sign.call_args_list)
261 self.assertEqual(1, signer_authorization.add_signature.call_count)
262 signature = signer_authorization.add_signature.call_args_list[0][0][0]
263 pubkey.verify_digest(bytes.fromhex(signature), bytes.fromhex("bb"*32),
264 sigdecode=ecdsa.util.sigdecode_der)
265 self.assertEqual([call("an-output-path")],
266 signer_authorization.save_to_jsonfile.call_args_list)
269@patch("signapp.SignerAuthorization")
270@patch("signapp.info")
271class TestSignAppManual(TestCase):
272 def test_ok(self, info_mock, signer_authorization_mock):
273 signer_authorization = Mock()
274 signer_authorization_mock.from_jsonfile.return_value = signer_authorization
276 with patch("sys.argv", ["signapp.py", "manual", "-o", "an-output-path",
277 "-g", "a-signature"]):
278 with self.assertRaises(SystemExit) as exit:
279 main()
281 self.assertEqual(exit.exception.code, RETURN_SUCCESS)
283 self.assertEqual([call("an-output-path")],
284 signer_authorization_mock.from_jsonfile.call_args_list)
285 self.assertEqual([call("a-signature")],
286 signer_authorization.add_signature.call_args_list)
287 self.assertEqual([call("an-output-path")],
288 signer_authorization.save_to_jsonfile.call_args_list)