Coverage for comm/bip32.py: 89%

76 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 struct 

24import logging 

25 

26_logger = logging.getLogger("bip44") 

27 

28 

29class BIP32Element: 

30 def __init__(self, spec): 

31 if type(spec) != str or len(spec) == 0: 

32 message = "BIP32Element spec must be a nonempty string" 

33 _logger.debug(message) 

34 raise ValueError(message) 

35 

36 index = 0 

37 sindex = spec 

38 if spec[-1] == "'": 

39 index = 1 << 31 

40 sindex = spec[:-1] 

41 

42 if not str.isdecimal(sindex): 

43 message = ( 

44 "BIP32Element must be a decimal number optionally followed by " 

45 "a single quote (got %s)" % sindex 

46 ) 

47 _logger.debug(message) 

48 raise ValueError(message) 

49 

50 val = int(sindex) 

51 if val >= (1 << 31): 

52 message = "BIP32Element must be specified with an integer between 0 and 2^31" 

53 _logger.debug(message) 

54 raise ValueError(message) 

55 

56 index += val 

57 

58 if index < 0 or index > (1 << 32): 

59 message = "Invalid index for BIP32 element" 

60 _logger.debug(message) 

61 raise ValueError(message) 

62 

63 self._index = index 

64 

65 @property 

66 def is_hardened(self): 

67 return self._index >= (1 << 31) 

68 

69 @property 

70 def spec_index(self): 

71 if self.is_hardened: 

72 return self._index - (1 << 31) 

73 return self._index 

74 

75 @property 

76 def index(self): 

77 return self._index 

78 

79 def __str__(self): 

80 return "%d%s" % (self.spec_index, "'" if self.is_hardened else "") 

81 

82 def __repr__(self): 

83 return '<BIP32Element "%s">' % str(self) 

84 

85 

86class BIP32Path: 

87 def __init__(self, spec, nelements=5): 

88 if type(spec) != str or len(spec) == 0: 

89 message = "BIP32Path spec must be a nonempty string" 

90 _logger.debug(message) 

91 raise ValueError(message) 

92 

93 if spec[:2] != "m/": 

94 message = "BIP32Path spec must start with 'm/', instead got %s" % spec 

95 _logger.debug(message) 

96 raise ValueError(message) 

97 

98 self._elements = list(map(BIP32Element, spec[2:].split("/"))) 

99 

100 if nelements is not None and len(self._elements) != nelements: 

101 message = "BIP32Path spec must have exactly %d elements, got %d" % ( 

102 nelements, 

103 len(self._elements), 

104 ) 

105 _logger.debug(message) 

106 raise ValueError(message) 

107 

108 @property 

109 def elements(self): 

110 return self._elements 

111 

112 def to_binary(self, byteorder="little"): 

113 if byteorder == "big": 

114 order_sign = ">" 

115 else: 

116 order_sign = "<" 

117 

118 binary = struct.pack(f"{order_sign}B", len(self._elements)) 

119 for element in self.elements: 

120 binary += struct.pack(f"{order_sign}I", element.index) 

121 return binary 

122 

123 def __str__(self): 

124 return "m/%s" % "/".join(map(str, self._elements)) 

125 

126 def __repr__(self): 

127 return '<BIP32Path "%s">' % str(self) 

128 

129 def __eq__(self, other): 

130 return str(self) == str(other)