def verify_balance_proof(self, sender, open_block_number, balance, signature): """Verify that a balance proof is valid and return the sender. Does not check the balance itself. :returns: the channel """ if (sender, open_block_number) in self.unconfirmed_channels: raise InsufficientConfirmations( 'Insufficient confirmations for the channel ' '(sender=%s, open_block_number=%d)' % (sender, open_block_number)) try: c = self.channels[sender, open_block_number] except KeyError: raise NoOpenChannel('Channel does not exist or has been closed' '(sender=%s, open_block_number=%s)' % (sender, open_block_number)) if c.is_closed: raise NoOpenChannel('Channel closing has been requested already.') if not is_same_address( verify_balance_proof(self.receiver, open_block_number, balance, decode_hex(signature)), sender): raise InvalidBalanceProof( 'Recovered signer does not match the sender') return c
def on_invalid_amount(self, method: str, url: str, response: Response, **kwargs) -> bool: log.debug('Server claims an invalid amount sent.') balance_sig = response.headers.get(HTTPHeaders.BALANCE_SIGNATURE) if balance_sig: balance_sig = decode_hex(balance_sig) last_balance = int(response.headers.get(HTTPHeaders.SENDER_BALANCE)) channel = self.get_channel(url) verified = balance_sig and is_same_address( verify_balance_proof( channel.receiver, channel.block, last_balance, balance_sig, self.client.channel_manager_address), channel.sender) if verified: if last_balance == channel.balance: log.error( 'Server tried to disguise the last unconfirmed payment as a confirmed payment.' ) return False else: log.debug( 'Server provided proof for a different channel balance ({}). Adopting.' .format(last_balance)) channel.update_balance(last_balance) else: log.debug( 'Server did not provide proof for a different channel balance. Reverting to 0.' ) channel.update_balance(0) return self.on_payment_requested(method, url, response, **kwargs)
def test_verify_balance_proof_v0(channel_manager_contract_address: str): sig = sign_balance_proof(SENDER_PRIVATE_KEY, RECEIVER_ADDR, 312524, 11, channel_manager_contract_address) sig = sig[:-1] + b'\x00' assert verify_balance_proof( RECEIVER_ADDR, 312524, 11, sig, channel_manager_contract_address) == SENDER_ADDR
def test_verify_balance_proof_v27(channel_manager_contract_address: str): # Should be default but test anyway. sig = sign_balance_proof(SENDER_PRIVATE_KEY, RECEIVER_ADDR, 312524, 11, channel_manager_contract_address) sig = sig[:-1] + b'\x1b' assert verify_balance_proof( RECEIVER_ADDR, 312524, 11, sig, channel_manager_contract_address) == SENDER_ADDR
def _sync_balance(self, balance: int, balance_sig: bytes) -> bool: if not self.channel: # Nothing to verify or sync. Server does not know about a channel yet. return True elif not balance: # Server does know about the channel but cannot confirm its creation yet. log.info( 'Server could not confirm new channel yet. Waiting for {} seconds.' .format(self.retry_interval)) time.sleep(self.retry_interval) self.retry = True return False verified = balance_sig and is_same_address( verify_balance_proof(self.channel.receiver, self.channel.block, balance, balance_sig), self.channel.sender) if balance > self.channel.balance: if verified: log.info( 'Server proved a higher channel balance (server/local): {}/{}. Adopting.' .format(balance, self.channel.balance)) self.channel.balance = balance else: log.error( 'Server could not prove higher channel balance (server/local): {}/{}' .format(balance, self.channel.balance)) return False elif balance < self.channel.balance: if verified: log.info( 'Server sent older balance proof or rejected the last one (server/local): ' '{}/{}. Possibly because of an unconfirmed topup. Retrying in {} seconds.' .format(balance, self.channel.balance, self.retry_interval)) self.channel.balance = balance time.sleep(self.retry_interval) self.retry = True return False else: log.info( 'Server sent lower balance without proof. Attempting to continue on lower ' 'balance (server/local): {}.'.format( balance, self.channel.balance)) self.channel.balance = balance return True
def close_cooperatively(self, closing_sig: bytes): """ Attempts to close the channel immediately by providing a hash of the channel's balance proof signed by the receiver. This signature must correspond to the balance proof stored in the passed channel state. """ if self.state == Channel.State.closed: log.error( 'Channel must not be closed already to be closed cooperatively.' ) return None log.info( 'Attempting to cooperatively close channel to {} created at block #{}.' .format(self.receiver, self.block)) current_block = self.client.web3.eth.blockNumber if not is_same_address( verify_balance_proof(self.receiver, self.block, self.balance, closing_sig), self.receiver): log.error('Invalid closing signature.') return None tx = self.client.channel_manager_proxy.create_signed_transaction( 'close', [ self.receiver, self.block, self.balance, self.balance_sig, closing_sig ]) self.client.web3.eth.sendRawTransaction(tx) log.info('Waiting for settle confirmation event...') event = self.client.channel_manager_proxy.get_channel_settle_event_blocking( self.sender, self.receiver, self.block, current_block + 1) if event: log.info('Successfully closed channel in block {}.'.format( event['blockNumber'])) self.state = Channel.State.closed self.client.store_channels() return event else: log.error('No event received.') return None
def test_verify_balance_proof(): sig = sign_balance_proof(SENDER_PRIVATE_KEY, RECEIVER_ADDR, 315123, 8) assert verify_balance_proof(RECEIVER_ADDR, 315123, 8, sig) == SENDER_ADDR
def test_verify_balance_proof_v27(): # Should be default but test anyway. sig = sign_balance_proof(SENDER_PRIVATE_KEY, RECEIVER_ADDR, 312524, 11) sig = sig[:-1] + b'\x1b' assert verify_balance_proof(RECEIVER_ADDR, 312524, 11, sig) == SENDER_ADDR
def test_verify_balance_proof_v0(): sig = sign_balance_proof(SENDER_PRIVATE_KEY, RECEIVER_ADDR, 312524, 11) sig = sig[:-1] + b'\x00' assert verify_balance_proof(RECEIVER_ADDR, 312524, 11, sig) == SENDER_ADDR
def test_verify_balance_proof(channel_manager_contract_address: str): sig = sign_balance_proof(SENDER_PRIVATE_KEY, RECEIVER_ADDR, 315123, 8, channel_manager_contract_address) assert verify_balance_proof( RECEIVER_ADDR, 315123, 8, sig, channel_manager_contract_address) == SENDER_ADDR