123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- ###############################################################################
- #
- # The MIT License (MIT)
- #
- # Copyright (c) Crossbar.io Technologies GmbH
- #
- # Permission is hereby granted, free of charge, to any person obtaining a copy
- # of this software and associated documentation files (the "Software"), to deal
- # in the Software without restriction, including without limitation the rights
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- # copies of the Software, and to permit persons to whom the Software is
- # furnished to do so, subject to the following conditions:
- #
- # The above copyright notice and this permission notice shall be included in
- # all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- # THE SOFTWARE.
- #
- ###############################################################################
-
- from binascii import a2b_hex
-
- import click
- import web3
- from py_eth_sig_utils import signing
-
- _EIP712_SIG_LEN = 32 + 32 + 1
-
-
- def unpack_uint128(data):
- assert data is None or type(data) == bytes, 'data must by bytes, was {}'.format(type(data))
- if data and type(data) == bytes:
- assert len(data) == 16, 'data must be bytes[16], but was bytes[{}]'.format(len(data))
-
- if data:
- return web3.Web3.toInt(data)
- else:
- return 0
-
-
- def pack_uint128(value):
- assert value is None or (type(value) == int and value >= 0 and value < 2**128)
-
- if value:
- data = web3.Web3.toBytes(value)
- return b'\x00' * (16 - len(data)) + data
- else:
- return b'\x00' * 16
-
-
- # FIXME: possibly use https://eth-abi.readthedocs.io/en/stable/decoding.html
-
- def unpack_uint256(data):
- assert data is None or type(data) == bytes, 'data must by bytes, was {}'.format(type(data))
- if data and type(data) == bytes:
- assert len(data) == 32, 'data must be bytes[32], but was bytes[{}]'.format(len(data))
-
- if data:
- return int(web3.Web3.toInt(data))
- else:
- return 0
-
-
- def pack_uint256(value):
- assert value is None or (type(value) == int and value >= 0 and value < 2**256), 'value must be uint256, but was {}'.format(value)
-
- if value:
- data = web3.Web3.toBytes(value)
- return b'\x00' * (32 - len(data)) + data
- else:
- return b'\x00' * 32
-
-
- def hl(text, bold=True, color='yellow'):
- if not isinstance(text, str):
- text = '{}'.format(text)
- return click.style(text, fg=color, bold=bold)
-
-
- def _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final):
- assert type(verifying_adr) == bytes and len(verifying_adr) == 20
- assert type(channel_adr) == bytes and len(channel_adr) == 20
- assert type(channel_seq) == int
- assert type(balance) == int
- assert type(is_final) == bool
-
- data = {
- 'types': {
- 'EIP712Domain': [
- {'name': 'name', 'type': 'string'},
- {'name': 'version', 'type': 'string'},
- {'name': 'chainId', 'type': 'uint256'},
- {'name': 'verifyingContract', 'type': 'address'},
- ],
- 'ChannelClose': [
- # The channel contract address.
- {'name': 'channel_adr', 'type': 'address'},
-
- # Channel off-chain transaction sequence number.
- {'name': 'channel_seq', 'type': 'uint32'},
-
- # Balance remaining in after the transaction.
- {'name': 'balance', 'type': 'uint256'},
-
- # Transaction is marked as final.
- {'name': 'is_final', 'type': 'bool'},
- ],
- },
- 'primaryType': 'ChannelClose',
- 'domain': {
- 'name': 'XBR',
- 'version': '1',
- 'chainId': 1,
- 'verifyingContract': verifying_adr,
- },
- 'message': {
- 'channel_adr': channel_adr,
- 'channel_seq': channel_seq,
- 'balance': balance,
- 'is_final': is_final
- },
- }
-
- return data
-
-
- def sign_eip712_data(eth_privkey, channel_adr, channel_seq, balance, is_final=False):
- """
-
- :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address).
- :type eth_privkey: bytes
-
- :param channel_adr: Channel contract address.
- :type channel_adr: bytes
-
- :param channel_seq: Payment channel off-chain transaction sequence number.
- :type channel_seq: int
-
- :param balance: Balance remaining in the payment/paying channel after buying/selling the key.
- :type balance: int
-
- :param is_final: Flag to indicate the transaction is considered final.
- :type is_final: bool
-
- :return: The signature according to EIP712 (32+32+1 raw bytes).
- :rtype: bytes
- """
- assert type(eth_privkey) == bytes and len(eth_privkey) == 32
- assert type(channel_adr) == bytes and len(channel_adr) == 20
- assert type(channel_seq) == int and channel_seq > 0
- assert type(balance) == int and balance >= 0
- assert type(is_final) == bool
-
- verifying_adr = a2b_hex('0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B'[2:])
-
- # make a private key object from the raw private key bytes
- # pkey = eth_keys.keys.PrivateKey(eth_privkey)
-
- # get the canonical address of the account
- # eth_adr = web3.Web3.toChecksumAddress(pkey.public_key.to_canonical_address())
- # eth_adr = pkey.public_key.to_canonical_address()
-
- # create EIP712 typed data object
- data = _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final)
-
- # FIXME: this fails on PyPy (but ot on CPy!) with
- # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b
- _args = signing.sign_typed_data(data, eth_privkey)
-
- signature = signing.v_r_s_to_signature(*_args)
- assert len(signature) == _EIP712_SIG_LEN
-
- return signature
-
-
- def recover_eip712_signer(channel_adr, channel_seq, balance, is_final, signature):
- """
- Recover the signer address the given EIP712 signature was signed with.
-
- :param channel_adr: Channel contract address.
- :type channel_adr: bytes
-
- :param channel_seq: Payment channel off-chain transaction sequence number.
- :type channel_seq: int
-
- :param balance: Balance remaining in the payment/paying channel after buying/selling the key.
- :type balance: int
-
- :param is_final: Flag to indicate the transaction is considered final.
- :type is_final: bool
-
- :param signature: The EIP712 (32+32+1 raw bytes) signature to verify.
- :type signature: bytes
-
- :return: The (computed) signer address the signature was signed with.
- :rtype: bytes
- """
- assert type(channel_adr) == bytes and len(channel_adr) == 20
- assert type(channel_seq) == int
- assert type(balance) == int
- assert type(is_final) == bool
- assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN
-
- verifying_adr = a2b_hex('0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B'[2:])
-
- # recreate EIP712 typed data object
- data = _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final)
-
- # this returns the signer (checksummed) address as a string, eg "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d"
- signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature))
-
- return a2b_hex(signer_address[2:])
|