|
|
|
# See the file "LICENSE" for information about the copyright
|
|
|
|
# and warranty status of this software.
|
|
|
|
|
|
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
import inspect
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from lib.hash import Base58, hash160, double_sha256
|
|
|
|
from lib.script import ScriptPubKey
|
|
|
|
from lib.tx import Deserializer
|
|
|
|
|
|
|
|
|
|
|
|
class CoinError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Coin(object):
|
|
|
|
'''Base class of coin hierarchy.'''
|
|
|
|
|
|
|
|
# Not sure if these are coin-specific
|
|
|
|
HEADER_LEN = 80
|
|
|
|
DEFAULT_RPC_PORT = 8332
|
|
|
|
VALUE_PER_COIN = 100000000
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def coins():
|
|
|
|
is_coin = lambda obj: (inspect.isclass(obj)
|
|
|
|
and issubclass(obj, Coin)
|
|
|
|
and obj != Coin)
|
|
|
|
pairs = inspect.getmembers(sys.modules[__name__], is_coin)
|
|
|
|
# Returned in the order they appear in this file
|
|
|
|
return [pair[1] for pair in pairs]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def lookup_coin_class(cls, name, net):
|
|
|
|
for coin in cls.coins():
|
|
|
|
if (coin.NAME.lower() == name.lower()
|
|
|
|
and coin.NET.lower() == net.lower()):
|
|
|
|
return coin
|
|
|
|
raise CoinError('unknown coin {} and network {} combination'
|
|
|
|
.format(name, net))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def lookup_xverbytes(verbytes):
|
|
|
|
# Order means BTC testnet will override NMC testnet
|
|
|
|
for coin in Coin.coins():
|
|
|
|
if verbytes == coin.XPUB_VERBYTES:
|
|
|
|
return True, coin
|
|
|
|
if verbytes == coin.XPRV_VERBYTES:
|
|
|
|
return False, coin
|
|
|
|
raise CoinError("version bytes unrecognised")
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def address_to_hash168(cls, addr):
|
|
|
|
'''Return a 21-byte hash given an address.
|
|
|
|
|
|
|
|
This is the hash160 prefixed by the address version byte.
|
|
|
|
'''
|
|
|
|
result = Base58.decode_check(addr)
|
|
|
|
if len(result) != 21:
|
|
|
|
raise CoinError('invalid address: {}'.format(addr))
|
|
|
|
return result
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def P2PKH_address_from_hash160(cls, hash_bytes):
|
|
|
|
'''Return a P2PKH address given a public key.'''
|
|
|
|
assert len(hash_bytes) == 20
|
|
|
|
payload = bytes([cls.P2PKH_VERBYTE]) + hash_bytes
|
|
|
|
return Base58.encode_check(payload)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def P2PKH_address_from_pubkey(cls, pubkey):
|
|
|
|
'''Return a coin address given a public key.'''
|
|
|
|
return cls.P2PKH_address_from_hash160(hash160(pubkey))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def P2SH_address_from_hash160(cls, pubkey_bytes):
|
|
|
|
'''Return a coin address given a public key.'''
|
|
|
|
assert len(hash_bytes) == 20
|
|
|
|
payload = bytes([cls.P2SH_VERBYTE]) + hash_bytes
|
|
|
|
return Base58.encode_check(payload)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def multisig_address(cls, m, pubkeys):
|
|
|
|
'''Return the P2SH address for an M of N multisig transaction.
|
|
|
|
|
|
|
|
Pass the N pubkeys of which M are needed to sign it. If
|
|
|
|
generating an address for a wallet, it is the caller's
|
|
|
|
responsibility to sort them to ensure order does not matter
|
|
|
|
for, e.g., wallet recovery.
|
|
|
|
'''
|
|
|
|
script = cls.pay_to_multisig_script(m, pubkeys)
|
|
|
|
payload = bytes([cls.P2SH_VERBYTE]) + hash160(pubkey_bytes)
|
|
|
|
return Base58.encode_check(payload)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def pay_to_multisig_script(cls, m, pubkeys):
|
|
|
|
'''Return a P2SH script for an M of N multisig transaction.'''
|
|
|
|
return ScriptPubKey.multisig_script(m, pubkeys)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def pay_to_pubkey_script(cls, pubkey):
|
|
|
|
'''Return a pubkey script that pays to a pubkey.
|
|
|
|
|
|
|
|
Pass the raw pubkey bytes (length 33 or 65).
|
|
|
|
'''
|
|
|
|
return ScriptPubKey.P2PK_script(pubkey)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def pay_to_address_script(cls, address):
|
|
|
|
'''Return a pubkey script that pays to a pubkey hash.
|
|
|
|
|
|
|
|
Pass the address (either P2PKH or P2SH) in base58 form.
|
|
|
|
'''
|
|
|
|
raw = Base58.decode_check(address)
|
|
|
|
|
|
|
|
# Require version byte plus hash160.
|
|
|
|
verbyte = -1
|
|
|
|
if len(raw) == 21:
|
|
|
|
verbyte, hash_bytes = raw[0], raw[1:]
|
|
|
|
|
|
|
|
if verbyte == cls.P2PKH_VERYBYTE:
|
|
|
|
return ScriptPubKey.P2PKH_script(hash_bytes)
|
|
|
|
if verbyte == cls.P2SH_VERBYTE:
|
|
|
|
return ScriptPubKey.P2SH_script(hash_bytes)
|
|
|
|
|
|
|
|
raise CoinError('invalid address: {}'.format(address))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def prvkey_WIF(privkey_bytes, compressed):
|
|
|
|
"Return the private key encoded in Wallet Import Format."
|
|
|
|
payload = bytearray([cls.WIF_BYTE]) + privkey_bytes
|
|
|
|
if compressed:
|
|
|
|
payload.append(0x01)
|
|
|
|
return Base58.encode_check(payload)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def header_hashes(cls, header):
|
|
|
|
'''Given a header return the previous block hash and the current block
|
|
|
|
hash.'''
|
|
|
|
return header[4:36], double_sha256(header)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def read_block(cls, block):
|
|
|
|
'''Read a block and return (header, tx_hashes, txs)'''
|
|
|
|
header, rest = block[:cls.HEADER_LEN], block[cls.HEADER_LEN:]
|
|
|
|
return (header, ) + Deserializer(rest).read_block()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def decimal_value(cls, value):
|
|
|
|
return Decimal(value) / cls.VALUE_PER_COIN
|
|
|
|
|
|
|
|
|
|
|
|
class Bitcoin(Coin):
|
|
|
|
NAME = "Bitcoin"
|
|
|
|
SHORTNAME = "BTC"
|
|
|
|
NET = "mainnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
|
|
|
P2PKH_VERBYTE = 0x00
|
|
|
|
P2SH_VERBYTE = 0x05
|
|
|
|
WIF_BYTE = 0x80
|
|
|
|
GENESIS_HASH=(b'000000000019d6689c085ae165831e93'
|
|
|
|
b'4ff763ae46a2a6c172b3f1b60a8ce26f')
|
|
|
|
TX_COUNT = 142791895
|
|
|
|
TX_COUNT_HEIGHT = 420976
|
|
|
|
TX_PER_BLOCK = 1600
|
|
|
|
|
|
|
|
class BitcoinTestnet(Coin):
|
|
|
|
NAME = "Bitcoin"
|
|
|
|
SHORTNAME = "XTN"
|
|
|
|
NET = "testnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("043587cf")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("04358394")
|
|
|
|
P2PKH_VERBYTE = 0x6f
|
|
|
|
P2SH_VERBYTE = 0xc4
|
|
|
|
WIF_BYTE = 0xef
|
|
|
|
|
|
|
|
# Source: pycoin and others
|
|
|
|
class Litecoin(Coin):
|
|
|
|
NAME = "Litecoin"
|
|
|
|
SHORTNAME = "LTC"
|
|
|
|
NET = "mainnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("019da462")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("019d9cfe")
|
|
|
|
P2PKH_VERBYTE = 0x30
|
|
|
|
P2SH_VERBYTE = 0x05
|
|
|
|
WIF_BYTE = 0xb0
|
|
|
|
|
|
|
|
class LitecoinTestnet(Coin):
|
|
|
|
NAME = "Litecoin"
|
|
|
|
SHORTNAME = "XLT"
|
|
|
|
NET = "testnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("0436f6e1")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("0436ef7d")
|
|
|
|
P2PKH_VERBYTE = 0x6f
|
|
|
|
P2SH_VERBYTE = 0xc4
|
|
|
|
WIF_BYTE = 0xef
|
|
|
|
|
|
|
|
# Source: namecoin.org
|
|
|
|
class Namecoin(Coin):
|
|
|
|
NAME = "Namecoin"
|
|
|
|
SHORTNAME = "NMC"
|
|
|
|
NET = "mainnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("d7dd6370")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("d7dc6e31")
|
|
|
|
P2PKH_VERBYTE = 0x34
|
|
|
|
P2SH_VERBYTE = 0x0d
|
|
|
|
WIF_BYTE = 0xe4
|
|
|
|
|
|
|
|
class NamecoinTestnet(Coin):
|
|
|
|
NAME = "Namecoin"
|
|
|
|
SHORTNAME = "XNM"
|
|
|
|
NET = "testnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("043587cf")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("04358394")
|
|
|
|
P2PKH_VERBYTE = 0x6f
|
|
|
|
P2SH_VERBYTE = 0xc4
|
|
|
|
WIF_BYTE = 0xef
|
|
|
|
|
|
|
|
# For DOGE there is disagreement across sites like bip32.org and
|
|
|
|
# pycoin. Taken from bip32.org and bitmerchant on github
|
|
|
|
class Dogecoin(Coin):
|
|
|
|
NAME = "Dogecoin"
|
|
|
|
SHORTNAME = "DOGE"
|
|
|
|
NET = "mainnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("02facafd")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("02fac398")
|
|
|
|
P2PKH_VERBYTE = 0x1e
|
|
|
|
P2SH_VERBYTE = 0x16
|
|
|
|
WIF_BYTE = 0x9e
|
|
|
|
|
|
|
|
class DogecoinTestnet(Coin):
|
|
|
|
NAME = "Dogecoin"
|
|
|
|
SHORTNAME = "XDT"
|
|
|
|
NET = "testnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("0432a9a8")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("0432a243")
|
|
|
|
P2PKH_VERBYTE = 0x71
|
|
|
|
P2SH_VERBYTE = 0xc4
|
|
|
|
WIF_BYTE = 0xf1
|
|
|
|
|
|
|
|
# Source: pycoin
|
|
|
|
class Dash(Coin):
|
|
|
|
NAME = "Dash"
|
|
|
|
SHORTNAME = "DASH"
|
|
|
|
NET = "mainnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("02fe52cc")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("02fe52f8")
|
|
|
|
P2PKH_VERBYTE = 0x4c
|
|
|
|
P2SH_VERBYTE = 0x10
|
|
|
|
WIF_BYTE = 0xcc
|
|
|
|
|
|
|
|
class DashTestnet(Coin):
|
|
|
|
NAME = "Dogecoin"
|
|
|
|
SHORTNAME = "tDASH"
|
|
|
|
NET = "testnet"
|
|
|
|
XPUB_VERBYTES = bytes.fromhex("3a805837")
|
|
|
|
XPRV_VERBYTES = bytes.fromhex("3a8061a0")
|
|
|
|
P2PKH_VERBYTE = 0x8b
|
|
|
|
P2SH_VERBYTE = 0x13
|
|
|
|
WIF_BYTE = 0xef
|