# 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 bitcoincash
import bitcoincash.base58
import bitcoincash.cashaddr
import bitcoincash.core
import bitcoincash.core.key
import bitcoincash.core.script as script
[docs]class CBitcoinAddressError(Exception):
"""Raised when an invalid Bitcoin address is encountered"""
[docs]class CBitcoinAddress(bitcoincash.cashaddr.CashAddrData):
"""A Bitcoin address"""
[docs] @classmethod
def from_bytes(cls, data, prefix, kind):
self = super(CBitcoinAddress, cls).from_bytes(data, prefix, kind)
if kind == bitcoincash.cashaddr.SCRIPT_TYPE:
self.__class__ = P2SHBitcoinAddress
elif kind == bitcoincash.cashaddr.PUBKEY_TYPE:
self.__class__ = P2PKHBitcoinAddress
else:
raise CBitcoinAddressError('Type %d not a recognized Bitcoin Address' % kind)
if prefix != bitcoincash.params.CASHADDR_PREFIX:
raise CBitcoinAddressError("The address looks valid, "
"however prefix does not match selected network params "
"(got {}, expected {}".format(
prefix, bitcoincash.params.CASHADDR_PREFIX))
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, ValueError):
pass
try:
return P2PKHBitcoinAddress.from_scriptPubKey(scriptPubKey)
except (CBitcoinAddressError, ValueError):
pass
raise CBitcoinAddressError('scriptPubKey not a valid address')
[docs] def to_scriptPubKey(self):
"""Convert an address to a scriptPubKey"""
raise NotImplementedError
[docs] def to_scriptHash(self, *, hashfunc = "sha256"):
"""A script hash is the hash of the binary bytes of the locking
script (ScriptPubKey), expressed as a hexadecimal string. Like
for block and transaction hashes, when converting the big-endian
binary hash to a hexadecimal string the least-significant byte
appears first, and the most-significant byte last.
This representation of an address is used in the Electrum protocol."""
if hashfunc != "sha256":
raise ValueError("Only sha256 hash function is supported")
import hashlib
return bitcoincash.core.b2lx(hashlib.sha256(self.to_scriptPubKey()).digest())
[docs]class P2SHBitcoinAddress(CBitcoinAddress):
[docs] @classmethod
def from_bytes(cls, data, prefix, kind):
if kind != bitcoincash.cashaddr.SCRIPT_TYPE:
raise ValueError('Address type encoding incorrect for P2SH address: got %d; expected %d' % \
(kind, bitcoincash.cashaddr.SCRIPT_TYPE))
return super(P2SHBitcoinAddress, cls).from_bytes(data, prefix, kind)
[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],
bitcoincash.params.CASHADDR_PREFIX, bitcoincash.cashaddr.SCRIPT_TYPE)
else:
raise CBitcoinAddressError('not a P2SH scriptPubKey')
[docs] def to_scriptPubKey(self):
"""Convert an address to a scriptPubKey"""
assert self.kind == bitcoincash.cashaddr.SCRIPT_TYPE
return script.CScript([script.OP_HASH160, self, script.OP_EQUAL])
[docs]class P2PKHBitcoinAddress(CBitcoinAddress):
[docs] @classmethod
def from_bytes(cls, data, prefix, kind):
if kind != bitcoincash.cashaddr.PUBKEY_TYPE:
raise ValueError('Address type incorrect for P2PKH address: got %d; expected %d' % \
(kind, bitcoincash.cashaddr.PUBKEY_TYPE))
return super(P2PKHBitcoinAddress, cls).from_bytes(data, prefix, kind)
[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, bitcoincash.core.key.CPubKey):
pubkey = bitcoincash.core.key.CPubKey(pubkey)
if not pubkey.is_fullyvalid:
raise CBitcoinAddressError('invalid pubkey')
pubkey_hash = bitcoincash.core.Hash160(pubkey)
return P2PKHBitcoinAddress.from_bytes(pubkey_hash,
bitcoincash.params.CASHADDR_PREFIX, bitcoincash.cashaddr.PUBKEY_TYPE)
[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 bitcoincash.core.script.CScriptInvalidError:
raise CBitcoinAddressError('not a P2PKH scriptPubKey: script is invalid')
if (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],
bitcoincash.params.CASHADDR_PREFIX, bitcoincash.cashaddr.PUBKEY_TYPE)
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):
"""Convert an address to a scriptPubKey"""
assert self.kind == bitcoincash.cashaddr.PUBKEY_TYPE
return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG])
[docs]class CKey(object):
"""An encapsulated secp256k1 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 = bitcoincash.core.key.CECKey()
self._cec_key.set_secretbytes(secret)
self._cec_key.set_compressed(compressed)
self.pub = bitcoincash.core.key.CPubKey(self._cec_key.get_pubkey(), self._cec_key)
@property
def is_compressed(self):
return self.pub.is_compressed
[docs] def signECDSA(self, hash):
return self._cec_key.sign(hash)
[docs] def sign_compactECDSA(self, hash):
return self._cec_key.sign_compact(hash)
[docs] def signSchnorr(self, hash):
"""
Create a Schnorr signature
"""
import bitcoincash.core.schnorr as schnorr
return schnorr.sign(self._cec_key.get_raw_privkey(), hash)
[docs]class CBitcoinSecretError(bitcoincash.base58.Base58Error):
pass
[docs]class CBitcoinSecret(bitcoincash.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''),
bitcoincash.params.BASE58_PREFIXES['SECRET_KEY'])
self.__init__(None)
return self
def __init__(self, s):
if self.nVersion != bitcoincash.params.BASE58_PREFIXES['SECRET_KEY']:
raise CBitcoinSecretError('Not a base58-encoded secret key: got nVersion=%d; expected nVersion=%d' % \
(self.nVersion, bitcoincash.params.BASE58_PREFIXES['SECRET_KEY']))
CKey.__init__(self, self[0:32], len(self) > 32 and _bord(self[32]) == 1)
[docs]def legacy_to_cashaddr(legacy_addr):
legacy = bitcoincash.base58.CBase58Data(legacy_addr)
if legacy.nVersion == bitcoincash.params.BASE58_PREFIXES['PUBKEY_ADDR']:
addr_type = bitcoincash.cashaddr.PUBKEY_TYPE
elif legacy.nVersion == bitcoincash.params.BASE58_PREFIXES['SCRIPT_ADDR']:
addr_type = bitcoincash.cashaddr.SCRIPT_TYPE
else:
raise CBitcoinAddressError("Unknown base58 address version {}".format(legacy.nVersion))
return bitcoincash.cashaddr.encode_full(
bitcoincash.params.CASHADDR_PREFIX, addr_type, legacy.to_bytes())
__all__ = (
'CBitcoinAddressError',
'CBitcoinAddress',
'P2SHBitcoinAddress',
'P2PKHBitcoinAddress',
'CKey',
'CBitcoinSecretError',
'CBitcoinSecret',
'legacy_to_cashaddr',
)