|
@ -1,6 +1,15 @@ |
|
|
# See the file "LICENSE" for information about the copyright |
|
|
# Copyright (c) 2016, Neil Booth |
|
|
|
|
|
# |
|
|
|
|
|
# All rights reserved. |
|
|
|
|
|
# |
|
|
|
|
|
# See the file "LICENCE" for information about the copyright |
|
|
# and warranty status of this software. |
|
|
# and warranty status of this software. |
|
|
|
|
|
|
|
|
|
|
|
'''Module providing coin abstraction. |
|
|
|
|
|
|
|
|
|
|
|
Anything coin-specific should go in this file and be subclassed where |
|
|
|
|
|
necessary for appropriate handling. |
|
|
|
|
|
''' |
|
|
|
|
|
|
|
|
from decimal import Decimal |
|
|
from decimal import Decimal |
|
|
import inspect |
|
|
import inspect |
|
@ -12,7 +21,7 @@ from lib.tx import Deserializer |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CoinError(Exception): |
|
|
class CoinError(Exception): |
|
|
pass |
|
|
'''Exception raised for coin-related errors.''' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Coin(object): |
|
|
class Coin(object): |
|
@ -24,17 +33,20 @@ class Coin(object): |
|
|
VALUE_PER_COIN = 100000000 |
|
|
VALUE_PER_COIN = 100000000 |
|
|
|
|
|
|
|
|
@staticmethod |
|
|
@staticmethod |
|
|
def coins(): |
|
|
def coin_classes(): |
|
|
|
|
|
'''Return a list of coin classes in declaration order.''' |
|
|
is_coin = lambda obj: (inspect.isclass(obj) |
|
|
is_coin = lambda obj: (inspect.isclass(obj) |
|
|
and issubclass(obj, Coin) |
|
|
and issubclass(obj, Coin) |
|
|
and obj != Coin) |
|
|
and obj != Coin) |
|
|
pairs = inspect.getmembers(sys.modules[__name__], is_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] |
|
|
return [pair[1] for pair in pairs] |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def lookup_coin_class(cls, name, net): |
|
|
def lookup_coin_class(cls, name, net): |
|
|
for coin in cls.coins(): |
|
|
'''Return a coin class given name and network. |
|
|
|
|
|
|
|
|
|
|
|
Raise an exception if unrecognised.''' |
|
|
|
|
|
for coin in cls.coin_classes(): |
|
|
if (coin.NAME.lower() == name.lower() |
|
|
if (coin.NAME.lower() == name.lower() |
|
|
and coin.NET.lower() == net.lower()): |
|
|
and coin.NET.lower() == net.lower()): |
|
|
return coin |
|
|
return coin |
|
@ -43,13 +55,14 @@ class Coin(object): |
|
|
|
|
|
|
|
|
@staticmethod |
|
|
@staticmethod |
|
|
def lookup_xverbytes(verbytes): |
|
|
def lookup_xverbytes(verbytes): |
|
|
|
|
|
'''Return a (is_xpub, coin_class) pair given xpub/xprv verbytes.''' |
|
|
# Order means BTC testnet will override NMC testnet |
|
|
# Order means BTC testnet will override NMC testnet |
|
|
for coin in Coin.coins(): |
|
|
for coin in Coin.coin_classes(): |
|
|
if verbytes == coin.XPUB_VERBYTES: |
|
|
if verbytes == coin.XPUB_VERBYTES: |
|
|
return True, coin |
|
|
return True, coin |
|
|
if verbytes == coin.XPRV_VERBYTES: |
|
|
if verbytes == coin.XPRV_VERBYTES: |
|
|
return False, coin |
|
|
return False, coin |
|
|
raise CoinError("version bytes unrecognised") |
|
|
raise CoinError('version bytes unrecognised') |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def address_to_hash168(cls, addr): |
|
|
def address_to_hash168(cls, addr): |
|
@ -129,7 +142,7 @@ class Coin(object): |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def prvkey_WIF(privkey_bytes, compressed): |
|
|
def prvkey_WIF(privkey_bytes, compressed): |
|
|
"Return the private key encoded in Wallet Import Format." |
|
|
'''Return the private key encoded in Wallet Import Format.''' |
|
|
payload = bytearray([cls.WIF_BYTE]) + privkey_bytes |
|
|
payload = bytearray([cls.WIF_BYTE]) + privkey_bytes |
|
|
if compressed: |
|
|
if compressed: |
|
|
payload.append(0x01) |
|
|
payload.append(0x01) |
|
@ -137,18 +150,22 @@ class Coin(object): |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def header_hashes(cls, header): |
|
|
def header_hashes(cls, header): |
|
|
'''Given a header return the previous block hash and the current block |
|
|
'''Given a header return the previous and current block hashes.''' |
|
|
hash.''' |
|
|
|
|
|
return header[4:36], double_sha256(header) |
|
|
return header[4:36], double_sha256(header) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def read_block(cls, block): |
|
|
def read_block(cls, block): |
|
|
'''Read a block and return (header, tx_hashes, txs)''' |
|
|
'''Return a tuple (header, tx_hashes, txs) given a raw block.''' |
|
|
header, rest = block[:cls.HEADER_LEN], block[cls.HEADER_LEN:] |
|
|
header, rest = block[:cls.HEADER_LEN], block[cls.HEADER_LEN:] |
|
|
return (header, ) + Deserializer(rest).read_block() |
|
|
return (header, ) + Deserializer(rest).read_block() |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def decimal_value(cls, value): |
|
|
def decimal_value(cls, value): |
|
|
|
|
|
'''Return the number of standard coin units as a Decimal given a |
|
|
|
|
|
quantity of smallest units. |
|
|
|
|
|
|
|
|
|
|
|
For example 1 BTC is returned for 100 million satoshis. |
|
|
|
|
|
''' |
|
|
return Decimal(value) / cls.VALUE_PER_COIN |
|
|
return Decimal(value) / cls.VALUE_PER_COIN |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -167,6 +184,7 @@ class Bitcoin(Coin): |
|
|
TX_COUNT_HEIGHT = 420976 |
|
|
TX_COUNT_HEIGHT = 420976 |
|
|
TX_PER_BLOCK = 1600 |
|
|
TX_PER_BLOCK = 1600 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BitcoinTestnet(Coin): |
|
|
class BitcoinTestnet(Coin): |
|
|
NAME = "Bitcoin" |
|
|
NAME = "Bitcoin" |
|
|
SHORTNAME = "XTN" |
|
|
SHORTNAME = "XTN" |
|
@ -177,6 +195,7 @@ class BitcoinTestnet(Coin): |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
WIF_BYTE = 0xef |
|
|
WIF_BYTE = 0xef |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Source: pycoin and others |
|
|
# Source: pycoin and others |
|
|
class Litecoin(Coin): |
|
|
class Litecoin(Coin): |
|
|
NAME = "Litecoin" |
|
|
NAME = "Litecoin" |
|
@ -188,6 +207,7 @@ class Litecoin(Coin): |
|
|
P2SH_VERBYTE = 0x05 |
|
|
P2SH_VERBYTE = 0x05 |
|
|
WIF_BYTE = 0xb0 |
|
|
WIF_BYTE = 0xb0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LitecoinTestnet(Coin): |
|
|
class LitecoinTestnet(Coin): |
|
|
NAME = "Litecoin" |
|
|
NAME = "Litecoin" |
|
|
SHORTNAME = "XLT" |
|
|
SHORTNAME = "XLT" |
|
@ -198,6 +218,7 @@ class LitecoinTestnet(Coin): |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
WIF_BYTE = 0xef |
|
|
WIF_BYTE = 0xef |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Source: namecoin.org |
|
|
# Source: namecoin.org |
|
|
class Namecoin(Coin): |
|
|
class Namecoin(Coin): |
|
|
NAME = "Namecoin" |
|
|
NAME = "Namecoin" |
|
@ -209,6 +230,7 @@ class Namecoin(Coin): |
|
|
P2SH_VERBYTE = 0x0d |
|
|
P2SH_VERBYTE = 0x0d |
|
|
WIF_BYTE = 0xe4 |
|
|
WIF_BYTE = 0xe4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NamecoinTestnet(Coin): |
|
|
class NamecoinTestnet(Coin): |
|
|
NAME = "Namecoin" |
|
|
NAME = "Namecoin" |
|
|
SHORTNAME = "XNM" |
|
|
SHORTNAME = "XNM" |
|
@ -219,6 +241,7 @@ class NamecoinTestnet(Coin): |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
WIF_BYTE = 0xef |
|
|
WIF_BYTE = 0xef |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# For DOGE there is disagreement across sites like bip32.org and |
|
|
# For DOGE there is disagreement across sites like bip32.org and |
|
|
# pycoin. Taken from bip32.org and bitmerchant on github |
|
|
# pycoin. Taken from bip32.org and bitmerchant on github |
|
|
class Dogecoin(Coin): |
|
|
class Dogecoin(Coin): |
|
@ -231,6 +254,7 @@ class Dogecoin(Coin): |
|
|
P2SH_VERBYTE = 0x16 |
|
|
P2SH_VERBYTE = 0x16 |
|
|
WIF_BYTE = 0x9e |
|
|
WIF_BYTE = 0x9e |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DogecoinTestnet(Coin): |
|
|
class DogecoinTestnet(Coin): |
|
|
NAME = "Dogecoin" |
|
|
NAME = "Dogecoin" |
|
|
SHORTNAME = "XDT" |
|
|
SHORTNAME = "XDT" |
|
@ -241,6 +265,7 @@ class DogecoinTestnet(Coin): |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
P2SH_VERBYTE = 0xc4 |
|
|
WIF_BYTE = 0xf1 |
|
|
WIF_BYTE = 0xf1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Source: pycoin |
|
|
# Source: pycoin |
|
|
class Dash(Coin): |
|
|
class Dash(Coin): |
|
|
NAME = "Dash" |
|
|
NAME = "Dash" |
|
@ -252,6 +277,7 @@ class Dash(Coin): |
|
|
P2SH_VERBYTE = 0x10 |
|
|
P2SH_VERBYTE = 0x10 |
|
|
WIF_BYTE = 0xcc |
|
|
WIF_BYTE = 0xcc |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DashTestnet(Coin): |
|
|
class DashTestnet(Coin): |
|
|
NAME = "Dogecoin" |
|
|
NAME = "Dogecoin" |
|
|
SHORTNAME = "tDASH" |
|
|
SHORTNAME = "tDASH" |
|
|