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

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 Mock, call, patch 

25from signapp import main 

26from admin.bip32 import BIP32Path 

27import ecdsa 

28import logging 

29 

30logging.disable(logging.CRITICAL) 

31 

32RETURN_SUCCESS = 0 

33RETURN_ERROR = 1 

34 

35 

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

41 

42 with patch("sys.argv", ["signapp.py", "hash", "-a", "a-path"]): 

43 with self.assertRaises(SystemExit) as exit: 

44 main() 

45 

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) 

51 

52 

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" 

64 

65 with patch("sys.argv", ["signapp.py", "message", "-a", "a-path", 

66 "-i", "an-iteration"]): 

67 with self.assertRaises(SystemExit) as exit: 

68 main() 

69 

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) 

75 

76 self.assertEqual([call("aabbcc", "an-iteration")], 

77 signer_version_mock.call_args_list) 

78 

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 

87 

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

92 

93 self.assertEqual(exit.exception.code, RETURN_SUCCESS) 

94 

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) 

101 

102 

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 

119 

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

125 

126 self.assertEqual(exit.exception.code, RETURN_SUCCESS) 

127 

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) 

142 

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 

152 

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

158 

159 self.assertEqual(exit.exception.code, RETURN_SUCCESS) 

160 

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) 

174 

175 

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

207 

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) 

225 

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 

244 

245 with patch("sys.argv", ["signapp.py", "eth", 

246 "-o", "an-output-path"]): 

247 with self.assertRaises(SystemExit) as exit: 

248 main() 

249 

250 self.assertEqual(exit.exception.code, RETURN_SUCCESS) 

251 

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) 

267 

268 

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 

275 

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

280 

281 self.assertEqual(exit.exception.code, RETURN_SUCCESS) 

282 

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)