You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_util.py 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) Crossbar.io Technologies GmbH
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. #
  25. ###############################################################################
  26. from binascii import a2b_hex
  27. import click
  28. import web3
  29. from py_eth_sig_utils import signing
  30. _EIP712_SIG_LEN = 32 + 32 + 1
  31. def unpack_uint128(data):
  32. assert data is None or type(data) == bytes, 'data must by bytes, was {}'.format(type(data))
  33. if data and type(data) == bytes:
  34. assert len(data) == 16, 'data must be bytes[16], but was bytes[{}]'.format(len(data))
  35. if data:
  36. return web3.Web3.toInt(data)
  37. else:
  38. return 0
  39. def pack_uint128(value):
  40. assert value is None or (type(value) == int and value >= 0 and value < 2**128)
  41. if value:
  42. data = web3.Web3.toBytes(value)
  43. return b'\x00' * (16 - len(data)) + data
  44. else:
  45. return b'\x00' * 16
  46. # FIXME: possibly use https://eth-abi.readthedocs.io/en/stable/decoding.html
  47. def unpack_uint256(data):
  48. assert data is None or type(data) == bytes, 'data must by bytes, was {}'.format(type(data))
  49. if data and type(data) == bytes:
  50. assert len(data) == 32, 'data must be bytes[32], but was bytes[{}]'.format(len(data))
  51. if data:
  52. return int(web3.Web3.toInt(data))
  53. else:
  54. return 0
  55. def pack_uint256(value):
  56. assert value is None or (type(value) == int and value >= 0 and value < 2**256), 'value must be uint256, but was {}'.format(value)
  57. if value:
  58. data = web3.Web3.toBytes(value)
  59. return b'\x00' * (32 - len(data)) + data
  60. else:
  61. return b'\x00' * 32
  62. def hl(text, bold=True, color='yellow'):
  63. if not isinstance(text, str):
  64. text = '{}'.format(text)
  65. return click.style(text, fg=color, bold=bold)
  66. def _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final):
  67. assert type(verifying_adr) == bytes and len(verifying_adr) == 20
  68. assert type(channel_adr) == bytes and len(channel_adr) == 20
  69. assert type(channel_seq) == int
  70. assert type(balance) == int
  71. assert type(is_final) == bool
  72. data = {
  73. 'types': {
  74. 'EIP712Domain': [
  75. {'name': 'name', 'type': 'string'},
  76. {'name': 'version', 'type': 'string'},
  77. {'name': 'chainId', 'type': 'uint256'},
  78. {'name': 'verifyingContract', 'type': 'address'},
  79. ],
  80. 'ChannelClose': [
  81. # The channel contract address.
  82. {'name': 'channel_adr', 'type': 'address'},
  83. # Channel off-chain transaction sequence number.
  84. {'name': 'channel_seq', 'type': 'uint32'},
  85. # Balance remaining in after the transaction.
  86. {'name': 'balance', 'type': 'uint256'},
  87. # Transaction is marked as final.
  88. {'name': 'is_final', 'type': 'bool'},
  89. ],
  90. },
  91. 'primaryType': 'ChannelClose',
  92. 'domain': {
  93. 'name': 'XBR',
  94. 'version': '1',
  95. 'chainId': 1,
  96. 'verifyingContract': verifying_adr,
  97. },
  98. 'message': {
  99. 'channel_adr': channel_adr,
  100. 'channel_seq': channel_seq,
  101. 'balance': balance,
  102. 'is_final': is_final
  103. },
  104. }
  105. return data
  106. def sign_eip712_data(eth_privkey, channel_adr, channel_seq, balance, is_final=False):
  107. """
  108. :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address).
  109. :type eth_privkey: bytes
  110. :param channel_adr: Channel contract address.
  111. :type channel_adr: bytes
  112. :param channel_seq: Payment channel off-chain transaction sequence number.
  113. :type channel_seq: int
  114. :param balance: Balance remaining in the payment/paying channel after buying/selling the key.
  115. :type balance: int
  116. :param is_final: Flag to indicate the transaction is considered final.
  117. :type is_final: bool
  118. :return: The signature according to EIP712 (32+32+1 raw bytes).
  119. :rtype: bytes
  120. """
  121. assert type(eth_privkey) == bytes and len(eth_privkey) == 32
  122. assert type(channel_adr) == bytes and len(channel_adr) == 20
  123. assert type(channel_seq) == int and channel_seq > 0
  124. assert type(balance) == int and balance >= 0
  125. assert type(is_final) == bool
  126. verifying_adr = a2b_hex('0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B'[2:])
  127. # make a private key object from the raw private key bytes
  128. # pkey = eth_keys.keys.PrivateKey(eth_privkey)
  129. # get the canonical address of the account
  130. # eth_adr = web3.Web3.toChecksumAddress(pkey.public_key.to_canonical_address())
  131. # eth_adr = pkey.public_key.to_canonical_address()
  132. # create EIP712 typed data object
  133. data = _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final)
  134. # FIXME: this fails on PyPy (but ot on CPy!) with
  135. # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b
  136. _args = signing.sign_typed_data(data, eth_privkey)
  137. signature = signing.v_r_s_to_signature(*_args)
  138. assert len(signature) == _EIP712_SIG_LEN
  139. return signature
  140. def recover_eip712_signer(channel_adr, channel_seq, balance, is_final, signature):
  141. """
  142. Recover the signer address the given EIP712 signature was signed with.
  143. :param channel_adr: Channel contract address.
  144. :type channel_adr: bytes
  145. :param channel_seq: Payment channel off-chain transaction sequence number.
  146. :type channel_seq: int
  147. :param balance: Balance remaining in the payment/paying channel after buying/selling the key.
  148. :type balance: int
  149. :param is_final: Flag to indicate the transaction is considered final.
  150. :type is_final: bool
  151. :param signature: The EIP712 (32+32+1 raw bytes) signature to verify.
  152. :type signature: bytes
  153. :return: The (computed) signer address the signature was signed with.
  154. :rtype: bytes
  155. """
  156. assert type(channel_adr) == bytes and len(channel_adr) == 20
  157. assert type(channel_seq) == int
  158. assert type(balance) == int
  159. assert type(is_final) == bool
  160. assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN
  161. verifying_adr = a2b_hex('0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B'[2:])
  162. # recreate EIP712 typed data object
  163. data = _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final)
  164. # this returns the signer (checksummed) address as a string, eg "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d"
  165. signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature))
  166. return a2b_hex(signer_address[2:])