Coverage for tests/admin/test_dongle_eth.py: 100%

109 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 

23import ecdsa 

24from admin.bip32 import BIP32Path 

25from admin.dongle_eth import DongleEth, DongleEthError 

26from ledgerblue.commException import CommException 

27import struct 

28from unittest import TestCase 

29from unittest.mock import call, Mock, patch 

30 

31 

32class TestDongleEth(TestCase): 

33 @classmethod 

34 def setUpClass(cls): 

35 privkey = ecdsa.SigningKey.from_string( 

36 bytes.fromhex("aa"*32), curve=ecdsa.SECP256k1) 

37 cls.pubkey = privkey.get_verifying_key().to_string("uncompressed") 

38 

39 @patch("admin.dongle_eth.getDongle") 

40 def setUp(self, getDongleMock): 

41 dongle_mock = Mock() 

42 getDongleMock.return_value = dongle_mock 

43 self.exchange_mock = dongle_mock.exchange 

44 self.eth = DongleEth(True) 

45 self.eth.connect() 

46 

47 def tearDown(self): 

48 self.eth.disconnect() 

49 

50 def test_get_pubkey_ok(self): 

51 self.exchange_mock.side_effect = [bytes([0x41]) + self.pubkey] 

52 

53 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

54 self.assertEqual(self.pubkey, self.eth.get_pubkey(eth_path)) 

55 

56 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

57 self.assertEqual([call(bytes([0xE0, 0x02, 0x00, 0x00, len(encoded_path) + 1, 

58 len(eth_path.elements)]) + encoded_path)], 

59 self.exchange_mock.call_args_list) 

60 

61 def test_get_pubkey_invalid_path(self): 

62 self.exchange_mock.side_effect = CommException("msg", 0x6a15) 

63 

64 eth_path = BIP32Path("m/44'/137'/0'/0/0") 

65 with self.assertRaises(DongleEthError): 

66 self.eth.get_pubkey(eth_path) 

67 

68 encoded_path = bytes.fromhex('8000002c80000089800000000000000000000000') 

69 self.assertEqual([call(bytes([0xE0, 0x02, 0x00, 0x00, len(encoded_path) + 1, 

70 len(eth_path.elements)]) + encoded_path)], 

71 self.exchange_mock.call_args_list) 

72 

73 def test_get_pubkey_wrong_app(self): 

74 self.exchange_mock.side_effect = CommException("msg", 0x6511) 

75 

76 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

77 with self.assertRaises(DongleEthError): 

78 self.eth.get_pubkey(eth_path) 

79 

80 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

81 self.assertEqual([call(bytes([0xE0, 0x02, 0x00, 0x00, len(encoded_path) + 1, 

82 len(eth_path.elements)]) + encoded_path)], 

83 self.exchange_mock.call_args_list) 

84 

85 def test_get_pubkey_device_locked(self): 

86 self.exchange_mock.side_effect = CommException("msg", 0x6b0c) 

87 

88 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

89 with self.assertRaises(DongleEthError): 

90 self.eth.get_pubkey(eth_path) 

91 

92 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

93 self.assertEqual([call(bytes([0xE0, 0x02, 0x00, 0x00, len(encoded_path) + 1, 

94 len(eth_path.elements)]) + encoded_path)], 

95 self.exchange_mock.call_args_list) 

96 

97 def test_get_pubkey_dongle_error(self): 

98 self.exchange_mock.side_effect = Exception('error-msg') 

99 

100 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

101 with self.assertRaises(DongleEthError): 

102 self.eth.get_pubkey(eth_path) 

103 

104 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

105 self.assertEqual([call(bytes([0xE0, 0x02, 0x00, 0x00, len(encoded_path) + 1, 

106 len(eth_path.elements)]) + encoded_path)], 

107 self.exchange_mock.call_args_list) 

108 

109 def test_sign_message_ok(self): 

110 v = 'aa' 

111 r = 'bb' * 32 

112 s = 'cc' * 32 

113 self.exchange_mock.side_effect = [bytes.fromhex(v + r + s)] 

114 

115 expected_signature = ecdsa.util.sigencode_der(int(r, 16), int(s, 16), 0) 

116 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

117 msg = ('aa' * 72).encode() 

118 self.assertEqual(expected_signature, self.eth.sign(eth_path, msg)) 

119 

120 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

121 encoded_tx = struct.pack(">I", len(msg)) + msg 

122 self.assertEqual([call(bytes([0xE0, 0x08, 0x00, 0x00, 

123 len(encoded_path) + 1 + len(encoded_tx), 

124 len(eth_path.elements)]) + encoded_path + encoded_tx)], 

125 self.exchange_mock.call_args_list) 

126 

127 def test_sign_message_invalid_path(self): 

128 self.exchange_mock.side_effect = CommException("msg", 0x6a15) 

129 

130 eth_path = BIP32Path("m/44'/137'/0'/0/0") 

131 msg = ('aa' * 72).encode() 

132 with self.assertRaises(DongleEthError): 

133 self.eth.sign(eth_path, msg) 

134 

135 encoded_path = bytes.fromhex('8000002c80000089800000000000000000000000') 

136 encoded_tx = struct.pack(">I", len(msg)) + msg 

137 self.assertEqual([call(bytes([0xE0, 0x08, 0x00, 0x00, 

138 len(encoded_path) + 1 + len(encoded_tx), 

139 len(eth_path.elements)]) + encoded_path + encoded_tx)], 

140 self.exchange_mock.call_args_list) 

141 

142 def test_sign_message_wrong_app(self): 

143 self.exchange_mock.side_effect = CommException("msg", 0x6511) 

144 

145 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

146 msg = ('aa' * 72).encode() 

147 with self.assertRaises(DongleEthError): 

148 self.eth.sign(eth_path, msg) 

149 

150 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

151 encoded_tx = struct.pack(">I", len(msg)) + msg 

152 self.assertEqual([call(bytes([0xE0, 0x08, 0x00, 0x00, 

153 len(encoded_path) + 1 + len(encoded_tx), 

154 len(eth_path.elements)]) + encoded_path + encoded_tx)], 

155 self.exchange_mock.call_args_list) 

156 

157 def test_sign_message_device_locked(self): 

158 self.exchange_mock.side_effect = CommException("msg", 0x6b0c) 

159 

160 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

161 msg = ('aa' * 72).encode() 

162 with self.assertRaises(DongleEthError): 

163 self.eth.sign(eth_path, msg) 

164 

165 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

166 encoded_tx = struct.pack(">I", len(msg)) + msg 

167 self.assertEqual([call(bytes([0xE0, 0x08, 0x00, 0x00, 

168 len(encoded_path) + 1 + len(encoded_tx), 

169 len(eth_path.elements)]) + encoded_path + encoded_tx)], 

170 self.exchange_mock.call_args_list) 

171 

172 def test_sign_message_dongle_error(self): 

173 self.exchange_mock.side_effect = Exception('error-msg') 

174 

175 eth_path = BIP32Path("m/44'/60'/0'/0/0") 

176 msg = ('aa' * 72).encode() 

177 with self.assertRaises(DongleEthError): 

178 self.eth.sign(eth_path, msg) 

179 

180 encoded_path = bytes.fromhex('8000002c8000003c800000000000000000000000') 

181 encoded_tx = struct.pack(">I", len(msg)) + msg 

182 self.assertEqual([call(bytes([0xE0, 0x08, 0x00, 0x00, 

183 len(encoded_path) + 1 + len(encoded_tx), 

184 len(eth_path.elements)]) + encoded_path + encoded_tx)], 

185 self.exchange_mock.call_args_list) 

186 

187 def test_sign_msg_too_big(self): 

188 ethpath = BIP32Path("m/44'/60'/0'/0/0") 

189 msg = ('aa' * 300).encode() 

190 with self.assertRaises(DongleEthError): 

191 self.eth.sign(ethpath, msg) 

192 

193 self.assertFalse(self.exchange_mock.called)