Source code for bitcoin.wallet

# Copyright (C) 2012-2014 The python-bitcoinlib developers
#
# This file is part of python-bitcoinlib.
#
# It is subject to the license terms in the LICENSE file found in the top-level
# directory of this distribution.
#
# No part of python-bitcoinlib, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in the
# LICENSE file.

"""Wallet-related functionality

Includes things like representing addresses and converting them to/from
scriptPubKeys; currently there is no actual wallet support implemented.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import sys

_bord = ord
if sys.version > '3':
    _bord = lambda x: x

import bitcoin
import bitcoin.base58
import bitcoin.core
import bitcoin.core.key
import bitcoin.core.script as script

[docs]class CBitcoinAddressError(bitcoin.base58.Base58Error): """Raised when an invalid Bitcoin address is encountered"""
[docs]class CBitcoinAddress(bitcoin.base58.CBase58Data): """A Bitcoin address"""
[docs] @classmethod def from_bytes(cls, data, nVersion): self = super(CBitcoinAddress, cls).from_bytes(data, nVersion) if nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR']: self.__class__ = P2SHBitcoinAddress elif nVersion == bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']: self.__class__ = P2PKHBitcoinAddress else: raise CBitcoinAddressError('Version %d not a recognized Bitcoin Address' % nVersion) return self
[docs] @classmethod def from_scriptPubKey(cls, scriptPubKey): """Convert a scriptPubKey to a CBitcoinAddress Returns a CBitcoinAddress subclass, either P2SHBitcoinAddress or P2PKHBitcoinAddress. If the scriptPubKey is not recognized CBitcoinAddressError will be raised. """ try: return P2SHBitcoinAddress.from_scriptPubKey(scriptPubKey) except CBitcoinAddressError: pass try: return P2PKHBitcoinAddress.from_scriptPubKey(scriptPubKey) except CBitcoinAddressError: pass raise CBitcoinAddressError('scriptPubKey not a valid address')
[docs] def to_scriptPubKey(self): """Convert an address to a scriptPubKey""" raise NotImplementedError
[docs]class P2SHBitcoinAddress(CBitcoinAddress):
[docs] @classmethod def from_bytes(cls, data, nVersion=None): if nVersion is None: nVersion = bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR'] elif nVersion != bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR']: raise ValueError('nVersion incorrect for P2SH address: got %d; expected %d' % \ (nVersion, bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR'])) return super(P2SHBitcoinAddress, cls).from_bytes(data, nVersion)
[docs] @classmethod def from_redeemScript(cls, redeemScript): """Convert a redeemScript to a P2SH address Convenience function: equivalent to P2SHBitcoinAddress.from_scriptPubKey(redeemScript.to_p2sh_scriptPubKey()) """ return cls.from_scriptPubKey(redeemScript.to_p2sh_scriptPubKey())
[docs] @classmethod def from_scriptPubKey(cls, scriptPubKey): """Convert a scriptPubKey to a P2SH address Raises CBitcoinAddressError if the scriptPubKey isn't of the correct form. """ if scriptPubKey.is_p2sh(): return cls.from_bytes(scriptPubKey[2:22], bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR']) else: raise CBitcoinAddressError('not a P2SH scriptPubKey')
[docs] def to_scriptPubKey(self): """Convert an address to a scriptPubKey""" assert self.nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR'] return script.CScript([script.OP_HASH160, self, script.OP_EQUAL])
[docs]class P2PKHBitcoinAddress(CBitcoinAddress):
[docs] @classmethod def from_bytes(cls, data, nVersion=None): if nVersion is None: nVersion = bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR'] elif nVersion != bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']: raise ValueError('nVersion incorrect for P2PKH address: got %d; expected %d' % \ (nVersion, bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR'])) return super(P2PKHBitcoinAddress, cls).from_bytes(data, nVersion)
[docs] @classmethod def from_pubkey(cls, pubkey, accept_invalid=False): """Create a P2PKH bitcoin address from a pubkey Raises CBitcoinAddressError if pubkey is invalid, unless accept_invalid is True. The pubkey must be a bytes instance; CECKey instances are not accepted. """ if not isinstance(pubkey, bytes): raise TypeError('pubkey must be bytes instance; got %r' % pubkey.__class__) if not accept_invalid: if not isinstance(pubkey, bitcoin.core.key.CPubKey): pubkey = bitcoin.core.key.CPubKey(pubkey) if not pubkey.is_fullyvalid: raise CBitcoinAddressError('invalid pubkey') pubkey_hash = bitcoin.core.Hash160(pubkey) return P2PKHBitcoinAddress.from_bytes(pubkey_hash)
[docs] @classmethod def from_scriptPubKey(cls, scriptPubKey, accept_non_canonical_pushdata=True, accept_bare_checksig=True): """Convert a scriptPubKey to a P2PKH address Raises CBitcoinAddressError if the scriptPubKey isn't of the correct form. accept_non_canonical_pushdata - Allow non-canonical pushes (default True) accept_bare_checksig - Treat bare-checksig as P2PKH scriptPubKeys (default True) """ if accept_non_canonical_pushdata: # Canonicalize script pushes scriptPubKey = script.CScript(scriptPubKey) # in case it's not a CScript instance yet try: scriptPubKey = script.CScript(tuple(scriptPubKey)) # canonicalize except bitcoin.core.script.CScriptInvalidError: raise CBitcoinAddressError('not a P2PKH scriptPubKey: script is invalid') if scriptPubKey.is_witness_v0_keyhash(): return cls.from_bytes(scriptPubKey[2:22], bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']) elif scriptPubKey.is_witness_v0_nested_keyhash(): return cls.from_bytes(scriptPubKey[3:23], bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']) elif (len(scriptPubKey) == 25 and _bord(scriptPubKey[0]) == script.OP_DUP and _bord(scriptPubKey[1]) == script.OP_HASH160 and _bord(scriptPubKey[2]) == 0x14 and _bord(scriptPubKey[23]) == script.OP_EQUALVERIFY and _bord(scriptPubKey[24]) == script.OP_CHECKSIG): return cls.from_bytes(scriptPubKey[3:23], bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']) elif accept_bare_checksig: pubkey = None # We can operate on the raw bytes directly because we've # canonicalized everything above. if (len(scriptPubKey) == 35 # compressed and _bord(scriptPubKey[0]) == 0x21 and _bord(scriptPubKey[34]) == script.OP_CHECKSIG): pubkey = scriptPubKey[1:34] elif (len(scriptPubKey) == 67 # uncompressed and _bord(scriptPubKey[0]) == 0x41 and _bord(scriptPubKey[66]) == script.OP_CHECKSIG): pubkey = scriptPubKey[1:65] if pubkey is not None: return cls.from_pubkey(pubkey, accept_invalid=True) raise CBitcoinAddressError('not a P2PKH scriptPubKey')
[docs] def to_scriptPubKey(self, nested=False): """Convert an address to a scriptPubKey""" assert self.nVersion == bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR'] return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG])
[docs]class CKey(object): """An encapsulated private key Attributes: pub - The corresponding CPubKey for this private key is_compressed - True if compressed """ def __init__(self, secret, compressed=True): self._cec_key = bitcoin.core.key.CECKey() self._cec_key.set_secretbytes(secret) self._cec_key.set_compressed(compressed) self.pub = bitcoin.core.key.CPubKey(self._cec_key.get_pubkey(), self._cec_key) @property def is_compressed(self): return self.pub.is_compressed
[docs] def sign(self, hash): return self._cec_key.sign(hash)
[docs] def sign_compact(self, hash): return self._cec_key.sign_compact(hash)
[docs]class CBitcoinSecretError(bitcoin.base58.Base58Error): pass
[docs]class CBitcoinSecret(bitcoin.base58.CBase58Data, CKey): """A base58-encoded secret key"""
[docs] @classmethod def from_secret_bytes(cls, secret, compressed=True): """Create a secret key from a 32-byte secret""" self = cls.from_bytes(secret + (b'\x01' if compressed else b''), bitcoin.params.BASE58_PREFIXES['SECRET_KEY']) self.__init__(None) return self
def __init__(self, s): if self.nVersion != bitcoin.params.BASE58_PREFIXES['SECRET_KEY']: raise CBitcoinSecretError('Not a base58-encoded secret key: got nVersion=%d; expected nVersion=%d' % \ (self.nVersion, bitcoin.params.BASE58_PREFIXES['SECRET_KEY'])) CKey.__init__(self, self[0:32], len(self) > 32 and _bord(self[32]) == 1)
__all__ = ( 'CBitcoinAddressError', 'CBitcoinAddress', 'P2SHBitcoinAddress', 'P2PKHBitcoinAddress', 'CKey', 'CBitcoinSecretError', 'CBitcoinSecret', )