You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
9.0 KiB
293 lines
9.0 KiB
from __future__ import (absolute_import, division, |
|
print_function, unicode_literals) |
|
from builtins import * # noqa: F401 |
|
|
|
|
|
from binascii import unhexlify |
|
from collections import OrderedDict |
|
import struct |
|
|
|
import jmbitcoin as btc |
|
from .configure import get_network |
|
|
|
|
|
TYPE_P2PKH, TYPE_P2SH_P2WPKH, TYPE_P2WPKH, TYPE_P2SH_M_N = range(4) |
|
NET_MAINNET, NET_TESTNET = range(2) |
|
NET_MAP = {'mainnet': NET_MAINNET, 'testnet': NET_TESTNET} |
|
WIF_PREFIX_MAP = {'mainnet': b'\x80', 'testnet': b'\xef'} |
|
BIP44_COIN_MAP = {'mainnet': 2**31, 'testnet': 2**31 + 1} |
|
|
|
def detect_script_type(script): |
|
if script.startswith(btc.P2PKH_PRE) and script.endswith(btc.P2PKH_POST) and\ |
|
len(script) == 0x14 + len(btc.P2PKH_PRE) + len(btc.P2PKH_POST): |
|
return TYPE_P2PKH |
|
elif (script.startswith(btc.P2SH_P2WPKH_PRE) and |
|
script.endswith(btc.P2SH_P2WPKH_POST) and |
|
len(script) == 0x14 + len(btc.P2SH_P2WPKH_PRE) + len( |
|
btc.P2SH_P2WPKH_POST)): |
|
return TYPE_P2SH_P2WPKH |
|
elif script.startswith(btc.P2WPKH_PRE) and\ |
|
len(script) == 0x14 + len(btc.P2WPKH_PRE): |
|
return TYPE_P2WPKH |
|
raise EngineError("Unknown script type for script '{}'" |
|
.format(hexlify(script))) |
|
|
|
class classproperty(object): |
|
""" |
|
from https://stackoverflow.com/a/5192374 |
|
""" |
|
def __init__(self, f): |
|
self.f = f |
|
|
|
def __get__(self, obj, owner): |
|
return self.f(owner) |
|
|
|
|
|
class SimpleLruCache(OrderedDict): |
|
""" |
|
note: python3.2 has a lru cache in functools |
|
""" |
|
def __init__(self, max_size): |
|
OrderedDict.__init__(self) |
|
assert max_size > 0 |
|
self.max_size = max_size |
|
|
|
def __setitem__(self, key, value): |
|
OrderedDict.__setitem__(self, key, value) |
|
self._adjust_size() |
|
|
|
def __getitem__(self, item): |
|
e = OrderedDict.__getitem__(self, item) |
|
del self[item] |
|
OrderedDict.__setitem__(self, item, e) |
|
return e |
|
|
|
def _adjust_size(self): |
|
while len(self) > self.max_size: |
|
self.popitem(last=False) |
|
|
|
|
|
# |
|
# library stuff end |
|
# |
|
|
|
|
|
class EngineError(Exception): |
|
pass |
|
|
|
|
|
class BTCEngine(object): |
|
# must be set by subclasses |
|
VBYTE = None |
|
__LRU_KEY_CACHE = SimpleLruCache(50) |
|
|
|
@classproperty |
|
def BIP32_priv_vbytes(cls): |
|
return btc.PRIVATE[NET_MAP[get_network()]] |
|
|
|
@classproperty |
|
def WIF_PREFIX(cls): |
|
return WIF_PREFIX_MAP[get_network()] |
|
|
|
@classproperty |
|
def BIP44_COIN_TYPE(cls): |
|
return BIP44_COIN_MAP[get_network()] |
|
|
|
@staticmethod |
|
def privkey_to_pubkey(privkey): |
|
return btc.privkey_to_pubkey(privkey, False) |
|
|
|
@staticmethod |
|
def address_to_script(addr): |
|
return unhexlify(btc.address_to_script(addr)) |
|
|
|
@classmethod |
|
def wif_to_privkey(cls, wif): |
|
raw = btc.b58check_to_bin(wif) |
|
vbyte = struct.unpack('B', btc.get_version_byte(wif))[0] |
|
|
|
if (struct.unpack('B', btc.BTC_P2PK_VBYTE[get_network()])[0] + struct.unpack('B', cls.WIF_PREFIX)[0]) & 0xff == vbyte: |
|
key_type = TYPE_P2PKH |
|
elif (struct.unpack('B', btc.BTC_P2SH_VBYTE[get_network()])[0] + struct.unpack('B', cls.WIF_PREFIX)[0]) & 0xff == vbyte: |
|
key_type = TYPE_P2SH_P2WPKH |
|
else: |
|
key_type = None |
|
|
|
return raw, key_type |
|
|
|
@classmethod |
|
def privkey_to_wif(cls, priv): |
|
return btc.bin_to_b58check(priv, cls.WIF_PREFIX) |
|
|
|
@classmethod |
|
def derive_bip32_master_key(cls, seed): |
|
# FIXME: slight encoding mess |
|
return btc.bip32_deserialize( |
|
btc.bip32_master_key(seed, vbytes=cls.BIP32_priv_vbytes)) |
|
|
|
@classmethod |
|
def derive_bip32_privkey(cls, master_key, path): |
|
assert len(path) > 1 |
|
return cls._walk_bip32_path(master_key, path)[-1] |
|
|
|
@classmethod |
|
def derive_bip32_pub_export(cls, master_key, path): |
|
priv = cls._walk_bip32_path(master_key, path) |
|
return btc.bip32_serialize(btc.raw_bip32_privtopub(priv)) |
|
|
|
@classmethod |
|
def derive_bip32_priv_export(cls, master_key, path): |
|
return btc.bip32_serialize(cls._walk_bip32_path(master_key, path)) |
|
|
|
@classmethod |
|
def _walk_bip32_path(cls, master_key, path): |
|
key = master_key |
|
for lvl in path[1:]: |
|
assert 0 <= lvl < 2**32 |
|
if (key, lvl) in cls.__LRU_KEY_CACHE: |
|
key = cls.__LRU_KEY_CACHE[(key, lvl)] |
|
else: |
|
cls.__LRU_KEY_CACHE[(key, lvl)] = btc.raw_bip32_ckd(key, lvl) |
|
key = cls.__LRU_KEY_CACHE[(key, lvl)] |
|
return key |
|
|
|
@classmethod |
|
def privkey_to_script(cls, privkey): |
|
pub = cls.privkey_to_pubkey(privkey) |
|
return cls.pubkey_to_script(pub) |
|
|
|
@classmethod |
|
def pubkey_to_script(cls, pubkey): |
|
raise NotImplementedError() |
|
|
|
@classmethod |
|
def privkey_to_address(cls, privkey): |
|
script = cls.privkey_to_script(privkey) |
|
return btc.script_to_address(script, cls.VBYTE) |
|
|
|
@classmethod |
|
def pubkey_to_address(cls, pubkey): |
|
script = cls.pubkey_to_script(pubkey) |
|
return btc.script_to_address(script, cls.VBYTE) |
|
|
|
@classmethod |
|
def pubkey_has_address(cls, pubkey, addr): |
|
ascript = cls.address_to_script(addr) |
|
return cls.pubkey_has_script(pubkey, ascript) |
|
|
|
@classmethod |
|
def pubkey_has_script(cls, pubkey, script): |
|
stype = detect_script_type(script) |
|
assert stype in ENGINES |
|
engine = ENGINES[stype] |
|
pscript = engine.pubkey_to_script(pubkey) |
|
return script == pscript |
|
|
|
@classmethod |
|
def sign_transaction(cls, tx, index, privkey, amount): |
|
raise NotImplementedError() |
|
|
|
@staticmethod |
|
def sign_message(privkey, message): |
|
""" |
|
args: |
|
privkey: bytes |
|
message: bytes |
|
returns: |
|
base64-encoded signature |
|
""" |
|
return btc.ecdsa_sign(message, privkey, True, False) |
|
|
|
@classmethod |
|
def script_to_address(cls, script): |
|
return btc.script_to_address(script, vbyte=cls.VBYTE) |
|
|
|
|
|
class BTC_P2PKH(BTCEngine): |
|
@classproperty |
|
def VBYTE(cls): |
|
return btc.BTC_P2PK_VBYTE[get_network()] |
|
|
|
@classmethod |
|
def pubkey_to_script(cls, pubkey): |
|
return btc.pubkey_to_p2pkh_script(pubkey) |
|
|
|
@classmethod |
|
def pubkey_to_script_code(cls, pubkey): |
|
raise EngineError("Script code does not apply to legacy wallets") |
|
|
|
@classmethod |
|
def sign_transaction(cls, tx, index, privkey, *args, **kwargs): |
|
hashcode = kwargs.get('hashcode') or btc.SIGHASH_ALL |
|
return btc.sign(btc.serialize(tx), index, privkey, |
|
hashcode=hashcode, amount=None, native=False) |
|
|
|
|
|
class BTC_P2SH_P2WPKH(BTCEngine): |
|
# FIXME: implement different bip32 key export prefixes like electrum? |
|
# see http://docs.electrum.org/en/latest/seedphrase.html#list-of-reserved-numbers |
|
|
|
@classproperty |
|
def VBYTE(cls): |
|
return btc.BTC_P2SH_VBYTE[get_network()] |
|
|
|
@classmethod |
|
def pubkey_to_script(cls, pubkey): |
|
return btc.pubkey_to_p2sh_p2wpkh_script(pubkey) |
|
|
|
@classmethod |
|
def pubkey_to_script_code(cls, pubkey): |
|
""" As per BIP143, the scriptCode for the p2wpkh |
|
case is "76a914+hash160(pub)+"88ac" as per the |
|
scriptPubKey of the p2pkh case. |
|
""" |
|
return btc.pubkey_to_p2pkh_script(pubkey, require_compressed=True) |
|
|
|
@classmethod |
|
def sign_transaction(cls, tx, index, privkey, amount, |
|
hashcode=btc.SIGHASH_ALL, **kwargs): |
|
assert amount is not None |
|
return btc.sign(btc.serialize(tx), index, privkey, |
|
hashcode=hashcode, amount=amount, native=False) |
|
|
|
class BTC_P2WPKH(BTCEngine): |
|
|
|
@classproperty |
|
def VBYTE(cls): |
|
"""Note that vbyte is needed in the native segwit case |
|
to decide the value of the 'human readable part' of the |
|
bech32 address. If it's 0 or 5 we use 'bc', else we use |
|
'tb' for testnet bitcoin; so it doesn't matter if we use |
|
the P2PK vbyte or the P2SH one. |
|
However, regtest uses 'bcrt' only (and fails on 'tb'), |
|
so bitcoin.script_to_address currently uses an artificial |
|
value 100 to flag that case. |
|
This means that for testing, this value must be explicitly |
|
overwritten. |
|
""" |
|
return btc.BTC_P2PK_VBYTE[get_network()] |
|
|
|
@classmethod |
|
def pubkey_to_script(cls, pubkey): |
|
return btc.pubkey_to_p2wpkh_script(pubkey) |
|
|
|
@classmethod |
|
def pubkey_to_script_code(cls, pubkey): |
|
""" As per BIP143, the scriptCode for the p2wpkh |
|
case is "76a914+hash160(pub)+"88ac" as per the |
|
scriptPubKey of the p2pkh case. |
|
""" |
|
return btc.pubkey_to_p2pkh_script(pubkey, require_compressed=True) |
|
|
|
@classmethod |
|
def sign_transaction(cls, tx, index, privkey, amount, |
|
hashcode=btc.SIGHASH_ALL, **kwargs): |
|
assert amount is not None |
|
return btc.sign(btc.serialize(tx), index, privkey, |
|
hashcode=hashcode, amount=amount, native=True) |
|
|
|
ENGINES = { |
|
TYPE_P2PKH: BTC_P2PKH, |
|
TYPE_P2SH_P2WPKH: BTC_P2SH_P2WPKH, |
|
TYPE_P2WPKH: BTC_P2WPKH |
|
} |