Closes #104 DEFAULT_PORTS now a coin property A Peer object maintains peer information Revamp LocalRPC "peers" call to show a lot more information Have lib/jsonrpc.py take care of handling request timeouts Save and restore peers to a file Loosen JSON RPC rules so we work with electrum-server and beancurd which don't follow the spec. Handle incoming server.add_peer requests Send server.add_peer registrations if peer doesn't have us or correct ports Verify peers at regular intervals, forget stale peers, verify new peers or those with updated ports If connecting via one port fails, try the other Add socks.py for SOCKS4 and SOCKS5 proxying, so Tor servers can now be reached by TCP and SSL Put full licence boilerplate in lib/ files Disable IRC advertising on testnet Serve a Tor banner file if it seems like a connection came from your tor proxy (see ENVIONMENT.rst) Retry tor proxy hourly, and peers that are about to turn stale Report more onion peers to a connection that seems to be combing from your tor proxy Only report good peers to server.peers.subscribe; always report self if valid Handle peers on the wrong network robustly Default to 127.0.0.1 rather than localhost for Python <= 3.5.2 compatibility Put peer name in logs of connections to it Update docs
158 lines
4.5 KiB
Python
158 lines
4.5 KiB
Python
# Copyright (c) 2016-2017, Neil Booth
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# The MIT License (MIT)
|
|
#
|
|
# 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.
|
|
|
|
'''Cryptograph hash functions and related classes.'''
|
|
|
|
|
|
import hashlib
|
|
import hmac
|
|
|
|
from lib.util import bytes_to_int, int_to_bytes
|
|
|
|
|
|
def sha256(x):
|
|
'''Simple wrapper of hashlib sha256.'''
|
|
assert isinstance(x, (bytes, bytearray, memoryview))
|
|
return hashlib.sha256(x).digest()
|
|
|
|
|
|
def ripemd160(x):
|
|
'''Simple wrapper of hashlib ripemd160.'''
|
|
assert isinstance(x, (bytes, bytearray, memoryview))
|
|
h = hashlib.new('ripemd160')
|
|
h.update(x)
|
|
return h.digest()
|
|
|
|
|
|
def double_sha256(x):
|
|
'''SHA-256 of SHA-256, as used extensively in bitcoin.'''
|
|
return sha256(sha256(x))
|
|
|
|
|
|
def hmac_sha512(key, msg):
|
|
'''Use SHA-512 to provide an HMAC.'''
|
|
return hmac.new(key, msg, hashlib.sha512).digest()
|
|
|
|
|
|
def hash160(x):
|
|
'''RIPEMD-160 of SHA-256.
|
|
|
|
Used to make bitcoin addresses from pubkeys.'''
|
|
return ripemd160(sha256(x))
|
|
|
|
|
|
def hash_to_str(x):
|
|
'''Convert a big-endian binary hash to displayed hex string.
|
|
|
|
Display form of a binary hash is reversed and converted to hex.
|
|
'''
|
|
return bytes(reversed(x)).hex()
|
|
|
|
|
|
def hex_str_to_hash(x):
|
|
'''Convert a displayed hex string to a binary hash.'''
|
|
return bytes(reversed(bytes.fromhex(x)))
|
|
|
|
|
|
class Base58Error(Exception):
|
|
'''Exception used for Base58 errors.'''
|
|
|
|
|
|
class Base58(object):
|
|
'''Class providing base 58 functionality.'''
|
|
|
|
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
assert len(chars) == 58
|
|
cmap = {c: n for n, c in enumerate(chars)}
|
|
|
|
@staticmethod
|
|
def char_value(c):
|
|
val = Base58.cmap.get(c)
|
|
if val is None:
|
|
raise Base58Error('invalid base 58 character "{}"'.format(c))
|
|
return val
|
|
|
|
@staticmethod
|
|
def decode(txt):
|
|
"""Decodes txt into a big-endian bytearray."""
|
|
if not isinstance(txt, str):
|
|
raise Base58Error('a string is required')
|
|
|
|
if not txt:
|
|
raise Base58Error('string cannot be empty')
|
|
|
|
value = 0
|
|
for c in txt:
|
|
value = value * 58 + Base58.char_value(c)
|
|
|
|
result = int_to_bytes(value)
|
|
|
|
# Prepend leading zero bytes if necessary
|
|
count = 0
|
|
for c in txt:
|
|
if c != '1':
|
|
break
|
|
count += 1
|
|
if count:
|
|
result = bytes(count) + result
|
|
|
|
return result
|
|
|
|
@staticmethod
|
|
def encode(be_bytes):
|
|
"""Converts a big-endian bytearray into a base58 string."""
|
|
value = bytes_to_int(be_bytes)
|
|
|
|
txt = ''
|
|
while value:
|
|
value, mod = divmod(value, 58)
|
|
txt += Base58.chars[mod]
|
|
|
|
for byte in be_bytes:
|
|
if byte != 0:
|
|
break
|
|
txt += '1'
|
|
|
|
return txt[::-1]
|
|
|
|
@staticmethod
|
|
def decode_check(txt):
|
|
'''Decodes a Base58Check-encoded string to a payload. The version
|
|
prefixes it.'''
|
|
be_bytes = Base58.decode(txt)
|
|
result, check = be_bytes[:-4], be_bytes[-4:]
|
|
if check != double_sha256(result)[:4]:
|
|
raise Base58Error('invalid base 58 checksum for {}'.format(txt))
|
|
return result
|
|
|
|
@staticmethod
|
|
def encode_check(payload):
|
|
"""Encodes a payload bytearray (which includes the version byte(s))
|
|
into a Base58Check string."""
|
|
assert isinstance(payload, (bytes, bytearray, memoryview))
|
|
|
|
be_bytes = payload + double_sha256(payload)[:4]
|
|
return Base58.encode(be_bytes)
|