Coverage for tests/admin/test_signer_authorization.py: 100%
105 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 patch, call, mock_open
25from admin.signer_authorization import SignerAuthorization, SignerVersion
26from comm.utils import keccak_256
27import json
29import logging
31logging.disable(logging.CRITICAL)
34class TestSignerAuthorization(TestCase):
35 def setUp(self):
36 self.sigs = [
37 "3044022039c6785195590cf80a39473a3c74196fb00768b4fa0afa42e542a2cdbf17a09102201f47eb7939da1dded637dfef6911d7c6f2c52943f02f32947620a1c82ecfb1e9", # noqa E501
38 "304402206d327be3539bd0187525420554f6087a50a7edab89bf69b001d40936bff41adf02206c46e02c7df30191eddbac780037bd6aed888a0cc09af02dac46afc8cbabe54a", # noqa E501
39 "3044022054440c5d33490590c7b75ec7c2f2756cded50796b8e5b984574656e5506cebd302200ac695c65c4b2d43af072fa7068b1119245a5a72ecfa920794f2fa82398f563d", # noqa E501
40 ]
42 self.sigver = SignerVersion("cc"*32, 123)
44 self.sa = SignerAuthorization(self.sigver, self.sigs)
46 def test_signer_version_n_signatures(self):
47 self.assertEqual(self.sa.signer_version.hash, "cc"*32)
48 self.assertEqual(self.sa.signer_version.iteration, 123)
49 self.assertEqual(self.sa.signatures, self.sigs)
50 self.assertIsNot(self.sa.signatures, self.sigs)
52 def test_invalid_signer_version(self):
53 with self.assertRaises(ValueError):
54 SignerAuthorization("not-a-signer-version", self.sigs)
56 def test_invalid_signatures(self):
57 with self.assertRaises(ValueError):
58 SignerAuthorization(self.sigver, "not-an-array")
60 def test_invalid_signature(self):
61 with self.assertRaises(ValueError):
62 SignerAuthorization(self.sigver, [self.sigs[0], "not-a-valid-signature"])
64 def test_to_dict(self):
65 self.assertEqual({
66 "version": 1,
67 "signer": {
68 "hash": "cc"*32,
69 "iteration": 123,
70 },
71 "signatures": self.sigs
72 }, self.sa.to_dict())
74 def test_add_signature(self):
75 new_sig = "304402206028c2917d0dfd66b92754750b4e2dbc6459de"\
76 "2dff598f0014470ee02e3c020702202baf9cab552b5021"\
77 "c7f3966fb7051be2ec1d273b3d5d1ce02e1ae73d1d8038ed"
78 self.sa.add_signature(new_sig)
80 self.assertEqual(self.sa.signatures, self.sigs + [new_sig])
82 def test_add_invalid_signature(self):
83 with self.assertRaises(ValueError):
84 self.sa.add_signature("invalid-signature")
86 def test_save_to_jsonfile(self):
87 with patch("builtins.open", mock_open()) as open_mock:
88 self.sa.save_to_jsonfile("/a/file/path.json")
90 self.assertEqual([call("/a/file/path.json", "w")], open_mock.call_args_list)
91 self.assertEqual([call(json.dumps(self.sa.to_dict(), indent=2) + "\n")],
92 open_mock.return_value.write.call_args_list)
94 def test_from_jsonfile(self):
95 jsonsample = """
96 {
97 "version": 1,
98 "signer": {
99 "hash": "0123456789012345678901234567890123456789012345678901234567891122",
100 "iteration": 345 },
101 "signatures": [
102 "3044022039e6db716cd2ce9efbd29a01afd50ffb04bae58ac747dc847b5af34bec03a195022060ffa2e7758a92a53093a672f3813d17352212dfab9535fd4927dbbf487d910a",
103 "304402201ef9d2a728e86aa3e8a0cf27a1f6afeba84af90f89ea50ea14483c4bd0c17fcd02201b6130ab0aed38128a4637b93ac90484aa2361c014e89c915d061fd27cab6aa6"
104 ]
105 }
106 """
108 with patch("builtins.open", mock_open()) as open_mock:
109 open_mock.return_value.read.return_value = jsonsample
111 sa = SignerAuthorization.from_jsonfile("/an/existing/file.json")
113 self.assertEqual([call("/an/existing/file.json", "r")], open_mock.call_args_list)
114 self.assertEqual(
115 "0123456789012345678901234567890123456789012345678901234567891122",
116 sa.signer_version.hash)
117 self.assertEqual(345, sa.signer_version.iteration)
118 self.assertEqual([
119 "3044022039e6db716cd2ce9efbd29a01afd50ffb04bae58ac747dc847b5af34bec03a195022060ffa2e7758a92a53093a672f3813d17352212dfab9535fd4927dbbf487d910a", # noqa E501
120 "304402201ef9d2a728e86aa3e8a0cf27a1f6afeba84af90f89ea50ea14483c4bd0c17fcd02201b6130ab0aed38128a4637b93ac90484aa2361c014e89c915d061fd27cab6aa6", # noqa E501
121 ], sa.signatures)
123 def test_from_jsonfile_invalid_json(self):
124 jsonsample = """
125 { THISISNOTJSON
126 "version": 1,
127 "signer": {
128 "hash": "0123456789012345678901234567890123456789012345678901234567891122",
129 "iteration": 345 },
130 "signatures": []
131 }
132 """
134 with patch("builtins.open", mock_open()) as open_mock:
135 open_mock.return_value.read.return_value = jsonsample
137 with self.assertRaises(ValueError):
138 SignerAuthorization.from_jsonfile("/an/existing/file.json")
140 def test_from_jsonfile_invalid_version(self):
141 jsonsample = """
142 {
143 "version": 2,
144 "signer": {
145 "hash": "0123456789012345678901234567890123456789012345678901234567891122",
146 "iteration": 345 },
147 "signatures": [
148 "3044022039e6db716cd2ce9efbd29a01afd50ffb04bae58ac747dc847b5af34bec03a195022060ffa2e7758a92a53093a672f3813d17352212dfab9535fd4927dbbf487d910a",
149 "304402201ef9d2a728e86aa3e8a0cf27a1f6afeba84af90f89ea50ea14483c4bd0c17fcd02201b6130ab0aed38128a4637b93ac90484aa2361c014e89c915d061fd27cab6aa6"
150 ]
151 }
152 """
154 with patch("builtins.open", mock_open()) as open_mock:
155 open_mock.return_value.read.return_value = jsonsample
157 with self.assertRaises(ValueError):
158 SignerAuthorization.from_jsonfile("/an/existing/file.json")
160 def test_from_jsonfile_invalid_hash(self):
161 jsonsample = """
162 {
163 "version": 1,
164 "signer": {
165 "hash": "not-a-hash",
166 "iteration": 345 },
167 "signatures": [
168 "3044022039e6db716cd2ce9efbd29a01afd50ffb04bae58ac747dc847b5af34bec03a195022060ffa2e7758a92a53093a672f3813d17352212dfab9535fd4927dbbf487d910a",
169 "304402201ef9d2a728e86aa3e8a0cf27a1f6afeba84af90f89ea50ea14483c4bd0c17fcd02201b6130ab0aed38128a4637b93ac90484aa2361c014e89c915d061fd27cab6aa6"
170 ]
171 }
172 """
174 with patch("builtins.open", mock_open()) as open_mock:
175 open_mock.return_value.read.return_value = jsonsample
177 with self.assertRaises(ValueError):
178 SignerAuthorization.from_jsonfile("/an/existing/file.json")
180 def test_from_jsonfile_invalid_iteration(self):
181 jsonsample = """
182 {
183 "version": 1,
184 "signer": {
185 "hash": "0123456789012345678901234567890123456789012345678901234567891122",
186 "iteration": "not-an-iteration" },
187 "signatures": [
188 "3044022039e6db716cd2ce9efbd29a01afd50ffb04bae58ac747dc847b5af34bec03a195022060ffa2e7758a92a53093a672f3813d17352212dfab9535fd4927dbbf487d910a",
189 "304402201ef9d2a728e86aa3e8a0cf27a1f6afeba84af90f89ea50ea14483c4bd0c17fcd02201b6130ab0aed38128a4637b93ac90484aa2361c014e89c915d061fd27cab6aa6"
190 ]
191 }
192 """
194 with patch("builtins.open", mock_open()) as open_mock:
195 open_mock.return_value.read.return_value = jsonsample
197 with self.assertRaises(ValueError):
198 SignerAuthorization.from_jsonfile("/an/existing/file.json")
200 def test_from_jsonfile_invalid_signatures(self):
201 jsonsample = """
202 {
203 "version": 1,
204 "signer": {
205 "hash": "0123456789012345678901234567890123456789012345678901234567891122",
206 "iteration": 345 },
207 "signatures": "not-signatures"
208 }
209 """
211 with patch("builtins.open", mock_open()) as open_mock:
212 open_mock.return_value.read.return_value = jsonsample
214 with self.assertRaises(ValueError):
215 SignerAuthorization.from_jsonfile("/an/existing/file.json")
217 def test_from_jsonfile_invalid_signature(self):
218 jsonsample = """
219 {
220 "version": 1,
221 "signer": {
222 "hash": "0123456789012345678901234567890123456789012345678901234567891122",
223 "iteration": 345 },
224 "signatures": [
225 "not-a-signature",
226 "304402201ef9d2a728e86aa3e8a0cf27a1f6afeba84af90f89ea50ea14483c4bd0c17fcd02201b6130ab0aed38128a4637b93ac90484aa2361c014e89c915d061fd27cab6aa6"
227 ]
228 }
229 """
231 with patch("builtins.open", mock_open()) as open_mock:
232 open_mock.return_value.read.return_value = jsonsample
234 with self.assertRaises(ValueError):
235 SignerAuthorization.from_jsonfile("/an/existing/file.json")
238class TestSignerVersion(TestCase):
239 def test_hash_iteration(self):
240 sv = SignerVersion("AA"*32, "0x2d")
242 self.assertEqual(sv.hash, "aa"*32)
243 self.assertEqual(sv.iteration, 45)
245 def test_invalid_hash(self):
246 with self.assertRaises(ValueError):
247 SignerVersion("notahash", 45)
249 def test_invalid_version(self):
250 with self.assertRaises(ValueError):
251 SignerVersion("aa"*32, "not-a-number")
253 def test_authorization_message(self):
254 sv = SignerVersion("aa" + "BB"*30 + "cc", "0x2d")
256 self.assertEqual(b"\x19Ethereum Signed Message:\n95RSK_powHSM_signer_aa"
257 b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
258 b"bbbbbbbbcc_iteration_45",
259 sv.get_authorization_msg())
261 def test_authorization_digest(self):
262 sv = SignerVersion("aa" + "BB"*30 + "cc", "0x2d")
264 self.assertEqual(
265 keccak_256(b"\x19Ethereum Signed Message:\n95RSK_powHSM_"
266 b"signer_aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
267 b"bbbbbbbbbbcc_iteration_45"),
268 sv.get_authorization_digest())
270 def test_to_dict(self):
271 sv = SignerVersion("aa" + "BB"*30 + "cc", "0x2d")
273 self.assertEqual({
274 "hash": "aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
275 "cc",
276 "iteration": 45,
277 }, sv.to_dict())