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

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. 

22 

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 

28 

29import logging 

30 

31logging.disable(logging.CRITICAL) 

32 

33 

34class TestSignerAuthorization(TestCase): 

35 def setUp(self): 

36 self.sigs = [ 

37 "3044022039c6785195590cf80a39473a3c74196fb00768b4fa0afa42e542a2cdbf17a09102201f47eb7939da1dded637dfef6911d7c6f2c52943f02f32947620a1c82ecfb1e9", # noqa E501 

38 "304402206d327be3539bd0187525420554f6087a50a7edab89bf69b001d40936bff41adf02206c46e02c7df30191eddbac780037bd6aed888a0cc09af02dac46afc8cbabe54a", # noqa E501 

39 "3044022054440c5d33490590c7b75ec7c2f2756cded50796b8e5b984574656e5506cebd302200ac695c65c4b2d43af072fa7068b1119245a5a72ecfa920794f2fa82398f563d", # noqa E501 

40 ] 

41 

42 self.sigver = SignerVersion("cc"*32, 123) 

43 

44 self.sa = SignerAuthorization(self.sigver, self.sigs) 

45 

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) 

51 

52 def test_invalid_signer_version(self): 

53 with self.assertRaises(ValueError): 

54 SignerAuthorization("not-a-signer-version", self.sigs) 

55 

56 def test_invalid_signatures(self): 

57 with self.assertRaises(ValueError): 

58 SignerAuthorization(self.sigver, "not-an-array") 

59 

60 def test_invalid_signature(self): 

61 with self.assertRaises(ValueError): 

62 SignerAuthorization(self.sigver, [self.sigs[0], "not-a-valid-signature"]) 

63 

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()) 

73 

74 def test_add_signature(self): 

75 new_sig = "304402206028c2917d0dfd66b92754750b4e2dbc6459de"\ 

76 "2dff598f0014470ee02e3c020702202baf9cab552b5021"\ 

77 "c7f3966fb7051be2ec1d273b3d5d1ce02e1ae73d1d8038ed" 

78 self.sa.add_signature(new_sig) 

79 

80 self.assertEqual(self.sa.signatures, self.sigs + [new_sig]) 

81 

82 def test_add_invalid_signature(self): 

83 with self.assertRaises(ValueError): 

84 self.sa.add_signature("invalid-signature") 

85 

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") 

89 

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) 

93 

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 """ 

107 

108 with patch("builtins.open", mock_open()) as open_mock: 

109 open_mock.return_value.read.return_value = jsonsample 

110 

111 sa = SignerAuthorization.from_jsonfile("/an/existing/file.json") 

112 

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) 

122 

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 """ 

133 

134 with patch("builtins.open", mock_open()) as open_mock: 

135 open_mock.return_value.read.return_value = jsonsample 

136 

137 with self.assertRaises(ValueError): 

138 SignerAuthorization.from_jsonfile("/an/existing/file.json") 

139 

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 """ 

153 

154 with patch("builtins.open", mock_open()) as open_mock: 

155 open_mock.return_value.read.return_value = jsonsample 

156 

157 with self.assertRaises(ValueError): 

158 SignerAuthorization.from_jsonfile("/an/existing/file.json") 

159 

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 """ 

173 

174 with patch("builtins.open", mock_open()) as open_mock: 

175 open_mock.return_value.read.return_value = jsonsample 

176 

177 with self.assertRaises(ValueError): 

178 SignerAuthorization.from_jsonfile("/an/existing/file.json") 

179 

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 """ 

193 

194 with patch("builtins.open", mock_open()) as open_mock: 

195 open_mock.return_value.read.return_value = jsonsample 

196 

197 with self.assertRaises(ValueError): 

198 SignerAuthorization.from_jsonfile("/an/existing/file.json") 

199 

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 """ 

210 

211 with patch("builtins.open", mock_open()) as open_mock: 

212 open_mock.return_value.read.return_value = jsonsample 

213 

214 with self.assertRaises(ValueError): 

215 SignerAuthorization.from_jsonfile("/an/existing/file.json") 

216 

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 """ 

230 

231 with patch("builtins.open", mock_open()) as open_mock: 

232 open_mock.return_value.read.return_value = jsonsample 

233 

234 with self.assertRaises(ValueError): 

235 SignerAuthorization.from_jsonfile("/an/existing/file.json") 

236 

237 

238class TestSignerVersion(TestCase): 

239 def test_hash_iteration(self): 

240 sv = SignerVersion("AA"*32, "0x2d") 

241 

242 self.assertEqual(sv.hash, "aa"*32) 

243 self.assertEqual(sv.iteration, 45) 

244 

245 def test_invalid_hash(self): 

246 with self.assertRaises(ValueError): 

247 SignerVersion("notahash", 45) 

248 

249 def test_invalid_version(self): 

250 with self.assertRaises(ValueError): 

251 SignerVersion("aa"*32, "not-a-number") 

252 

253 def test_authorization_message(self): 

254 sv = SignerVersion("aa" + "BB"*30 + "cc", "0x2d") 

255 

256 self.assertEqual(b"\x19Ethereum Signed Message:\n95RSK_powHSM_signer_aa" 

257 b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 

258 b"bbbbbbbbcc_iteration_45", 

259 sv.get_authorization_msg()) 

260 

261 def test_authorization_digest(self): 

262 sv = SignerVersion("aa" + "BB"*30 + "cc", "0x2d") 

263 

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()) 

269 

270 def test_to_dict(self): 

271 sv = SignerVersion("aa" + "BB"*30 + "cc", "0x2d") 

272 

273 self.assertEqual({ 

274 "hash": "aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 

275 "cc", 

276 "iteration": 45, 

277 }, sv.to_dict())