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.
 
 
 
 

490 lines
19 KiB

# note, only used for non-cryptographic randomness:
import random
import json
# needed for single sha256 evaluation, which is used
# in bitcoin (p2wsh) but not exposed in python-bitcointx:
import hashlib
from jmbitcoin.secp256k1_main import *
from jmbase import bintohex, utxo_to_utxostr
from bitcointx.core import (CMutableTransaction, CTxInWitness,
CMutableOutPoint, CMutableTxIn, CTransaction,
CMutableTxOut, CTxIn, CTxOut, ValidationError)
from bitcointx.core.script import *
from bitcointx.wallet import (P2WPKHCoinAddress, CCoinAddress, P2PKHCoinAddress,
CCoinAddressError)
from bitcointx.core.scripteval import (VerifyScript, SCRIPT_VERIFY_WITNESS,
SCRIPT_VERIFY_P2SH,
SCRIPT_VERIFY_STRICTENC,
SIGVERSION_WITNESS_V0)
# for each transaction type, different output script pubkeys may result in
# a difference in the number of bytes accounted for while estimating the
# transaction size, this variable stores the difference and is factored in
# when calculating the correct transaction size. For example, for a p2pkh
# transaction, if one of the outputs is a p2wsh pubkey, then the transaction
# would need 9 extra bytes to account for the difference in script pubkey
# sizes
OUTPUT_EXTRA_BYTES = {
'p2pkh': {
'p2wpkh': -3,
'p2sh-p2wpkh': -2,
'p2wsh': 9
},
'p2wpkh': {
'p2pkh': 3,
'p2sh-p2wpkh': 1,
'p2wsh': 12
},
'p2sh-p2wpkh': {
'p2pkh': 2,
'p2wpkh': -1,
'p2wsh': 11
}
}
def human_readable_transaction(tx, jsonified=True):
""" Given a CTransaction object, output a human
readable json-formatted string (suitable for terminal
output or large GUI textbox display) containing
all details of that transaction.
If `jsonified` is False, the dict is returned, instead
of the json string.
"""
assert isinstance(tx, CTransaction)
outdict = {}
outdict["hex"] = bintohex(tx.serialize())
outdict["inputs"]=[]
outdict["outputs"]=[]
outdict["txid"]= bintohex(tx.GetTxid()[::-1])
outdict["nLockTime"] = tx.nLockTime
outdict["nVersion"] = tx.nVersion
for i, inp in enumerate(tx.vin):
if not tx.wit.vtxinwit:
# witness section is not initialized/empty
witarg = None
else:
witarg = tx.wit.vtxinwit[i]
outdict["inputs"].append(human_readable_input(inp, witarg))
for i, out in enumerate(tx.vout):
outdict["outputs"].append(human_readable_output(out))
if not jsonified:
return outdict
return json.dumps(outdict, indent=4)
def human_readable_input(txinput, txinput_witness):
""" Pass objects of type CTxIn and CTxInWitness (or None)
and a dict of human-readable entries for this input
is returned.
"""
assert isinstance(txinput, CTxIn)
outdict = {}
success, u = utxo_to_utxostr((txinput.prevout.hash[::-1],
txinput.prevout.n))
assert success
outdict["outpoint"] = u
outdict["scriptSig"] = bintohex(txinput.scriptSig)
outdict["nSequence"] = txinput.nSequence
if txinput_witness:
outdict["witness"] = bintohex(
txinput_witness.scriptWitness.serialize())
return outdict
def human_readable_output(txoutput):
""" Returns a dict of human-readable entries
for this output.
"""
assert isinstance(txoutput, CTxOut)
outdict = {}
outdict["value_sats"] = txoutput.nValue
outdict["scriptPubKey"] = bintohex(txoutput.scriptPubKey)
try:
addr = CCoinAddress.from_scriptPubKey(txoutput.scriptPubKey)
outdict["address"] = str(addr)
except CCoinAddressError:
pass # non standard script
return outdict
def there_is_one_segwit_input(x):
# note that we need separate input types for
# any distinct types of scripthash inputs supported,
# since each may have a different size of witness; in
# that case, the internal list in this list comprehension
# will need updating.
return any([y in ["p2sh-p2wpkh", "p2wpkh", "p2wsh"] for y in x])
def estimate_tx_size(ins, outs):
'''Estimate transaction size.
Both arguments `ins` and `outs` must be lists of script types,
and they must be present in the keys of the dicts `inmults`,
`outmults` defined here.
Note that variation in ECDSA signature sizes means
we will sometimes see small inaccuracies in this estimate, but
that this is ameliorated by the existence of the witness discount,
in actually estimating fees.
The value '72' is used for the most-likely size of these ECDSA
signatures, due to 30[1 byte] + len(rest)[1 byte] + type:02 [1 byte] + len(r)[1] + r[32 or 33] + type:02[1] + len(s)[1] + s[32] + sighash_all [1]
... though as can be seen, 71 is also likely:
r length 33 occurs when the value is 'negative' (>N/2) and a byte x80 is prepended,
but shorter values for r are possible if rare.
Returns:
Either a single integer, if the transaction will be non-segwit,
or a tuple (int, int) for witness and non-witness bytes respectively).
'''
# All non-witness input sizes include: txid, index, sequence,
# which is 32, 4 and 4; the remaining is scriptSig which is 1
# at minimum, for native segwit (the byte x00). Hence 41 is the minimum.
# The witness field for p2wpkh consists of sig, pub so 72 + 33 + 1 byte
# for the number of witness elements and 2 bytes for the size of each element,
# hence 108.
# For p2pkh, 148 comes from 32+4+1+1+~72+1+33+4
# For p2sh-p2wpkh there is an additional 23 bytes of witness for the redeemscript.
#
# Note that p2wsh here is specific to the script
# we use for fidelity bonds; 43 is the bytes required for that
# script's redeemscript field in the witness, but for arbitrary scripts,
# the witness portion could be any other size.
# Hence, we may need to modify this later.
inmults = {"p2wsh": {"w": 1 + 72 + 43, "nw": 41},
"p2wpkh": {"w": 108, "nw": 41},
"p2sh-p2wpkh": {"w": 108, "nw": 64},
"p2pkh": {"w": 0, "nw": 148}}
# Notes: in outputs, there is only 1 'scripthash'
# type for either segwit/nonsegwit.
# p2wsh has structure 8 bytes output, then:
# x22,x00,x20,(32 byte hash), so 32 + 3 + 8
# note also there is no need to distinguish witness
# here, outputs are always entirely nonwitness.
outmults = {"p2wsh": 43,
"p2wpkh": 31,
"p2sh-p2wpkh": 64,
"p2pkh": 34}
# nVersion, nLockTime, nins, nouts:
nwsize = 4 + 4 + 2
wsize = 0
tx_is_segwit = there_is_one_segwit_input(ins)
if tx_is_segwit:
# flag and marker bytes
nwsize += 2
for i in ins:
if i not in inmults:
raise NotImplementedError(
"Script type not supported for transaction size "
"estimation: {}".format(i))
inmult = inmults[i]
nwsize += inmult["nw"]
wsize += inmult["w"]
for o in outs:
if o not in outmults:
raise NotImplementedError(
"Script type not supported for transaction size "
"estimation: {}".format(o))
nwsize += outmults[o]
if not tx_is_segwit:
return nwsize
return (wsize, nwsize)
def pubkey_to_p2pkh_script(pub, require_compressed=False):
"""
Given a pubkey in bytes, return a CScript
representing the corresponding pay-to-pubkey-hash
scriptPubKey.
"""
return P2PKHCoinAddress.from_pubkey(pub).to_scriptPubKey()
def pubkey_to_p2wpkh_script(pub):
"""
Given a pubkey in bytes (compressed), return a CScript
representing the corresponding pay-to-witness-pubkey-hash
scriptPubKey.
"""
return P2WPKHCoinAddress.from_pubkey(pub).to_scriptPubKey()
def pubkey_to_p2sh_p2wpkh_script(pub):
"""
Given a pubkey in bytes, return a CScript representing
the corresponding nested pay to witness keyhash
scriptPubKey.
"""
if not is_valid_pubkey(pub, True):
raise Exception("Invalid pubkey")
return pubkey_to_p2wpkh_script(pub).to_p2sh_scriptPubKey()
def redeem_script_to_p2wsh_script(redeem_script):
""" Given redeem script of type CScript (or bytes)
returns the corresponding segwit v0 scriptPubKey as
for the case pay-to-witness-scripthash.
"""
return standard_witness_v0_scriptpubkey(
hashlib.sha256(redeem_script).digest())
def mk_freeze_script(pub, locktime):
"""
Given a pubkey and locktime, create a script which can only be spent
after the locktime has passed using OP_CHECKLOCKTIMEVERIFY
"""
if not isinstance(locktime, int):
raise TypeError("locktime must be int")
if not isinstance(pub, bytes):
raise TypeError("pubkey must be in bytes")
if not is_valid_pubkey(pub, require_compressed=True):
raise ValueError("not a valid public key")
return CScript([locktime, OP_CHECKLOCKTIMEVERIFY, OP_DROP, pub,
OP_CHECKSIG])
def mk_burn_script(data):
""" For a given bytestring (data),
returns a scriptPubKey which is an OP_RETURN
of that data.
"""
if not isinstance(data, bytes):
raise TypeError("data must be in bytes")
return CScript([OP_RETURN, data])
def sign(tx, i, priv, hashcode=SIGHASH_ALL, amount=None, native=False):
"""
Given a transaction tx of type CMutableTransaction, an input index i,
and a raw privkey in bytes, updates the CMutableTransaction to contain
the newly appended signature.
Only four scriptPubKey types supported: p2pkh, p2wpkh, p2sh-p2wpkh, p2wsh.
Note that signing multisig must be done outside this function, using
the wrapped library.
If native is not the default (False), and if native != "p2wpkh",
then native must be a CScript object containing the redeemscript needed to sign.
Returns: (signature, "signing succeeded")
or: (None, errormsg) in case of failure
"""
# script verification flags
flags = set([SCRIPT_VERIFY_STRICTENC])
def return_err(e):
return None, "Error in signing: " + repr(e)
assert isinstance(tx, CMutableTransaction)
pub = privkey_to_pubkey(priv)
if not amount:
# p2pkh only supported here:
input_scriptPubKey = pubkey_to_p2pkh_script(pub)
sighash = SignatureHash(input_scriptPubKey, tx, i, hashcode)
try:
sig = ecdsa_raw_sign(sighash, priv, rawmsg=True) + bytes([hashcode])
except Exception as e:
return return_err(e)
tx.vin[i].scriptSig = CScript([sig, pub])
# Verify the signature worked.
try:
VerifyScript(tx.vin[i].scriptSig,
input_scriptPubKey, tx, i, flags=flags)
except Exception as e:
return return_err(e)
return sig, "signing succeeded"
else:
# segwit case; we currently support p2wpkh native or under p2sh.
# https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252
flags.add(SCRIPT_VERIFY_P2SH)
flags.add(SCRIPT_VERIFY_WITNESS)
if native and native != "p2wpkh":
scriptCode = native
input_scriptPubKey = redeem_script_to_p2wsh_script(native)
else:
# this covers both p2wpkh and p2sh-p2wpkh case:
input_scriptPubKey = pubkey_to_p2wpkh_script(pub)
# only created for convenience access to scriptCode:
input_address = P2WPKHCoinAddress.from_scriptPubKey(
input_scriptPubKey)
# function name is misleading here; redeemScript only applies to p2sh.
scriptCode = input_address.to_redeemScript()
sighash = SignatureHash(scriptCode, tx, i, hashcode, amount=amount,
sigversion=SIGVERSION_WITNESS_V0)
try:
sig = ecdsa_raw_sign(sighash, priv, rawmsg=True) + bytes([hashcode])
except Exception as e:
return return_err(e)
if not native:
tx.vin[i].scriptSig = CScript([input_scriptPubKey])
input_scriptPubKey = pubkey_to_p2sh_p2wpkh_script(pub)
if native and native != "p2wpkh":
witness = [sig, scriptCode]
else:
witness = [sig, pub]
ctxwitness = CTxInWitness(CScriptWitness(witness))
tx.wit.vtxinwit[i] = ctxwitness
# Verify the signature worked.
try:
VerifyScript(tx.vin[i].scriptSig, input_scriptPubKey, tx, i,
flags=flags, amount=amount, witness=tx.wit.vtxinwit[i].scriptWitness)
except ValidationError as e:
return return_err(e)
return sig, "signing succeeded"
def mktx(ins, outs, version=1, locktime=0):
""" Given a list of input tuples (txid(bytes), n(int)),
and a list of outputs which are dicts with
keys "address" (value should be *str* not CCoinAddress) (
or alternately "script" (for nonstandard outputs, value
should be CScript)),
"value" (value should be integer satoshis), outputs a
CMutableTransaction object.
Tx version and locktime are optionally set, for non-default
locktimes, inputs are given nSequence as per below comment.
"""
vin = []
vout = []
# This does NOT trigger rbf and mimics Core's standard behaviour as of
# Jan 2019.
# Tx creators wishing to use rbf will need to set it explicitly outside
# of this function.
if locktime != 0:
sequence = 0xffffffff - 1
else:
sequence = 0xffffffff
for i in ins:
outpoint = CMutableOutPoint((i[0][::-1]), i[1])
inp = CMutableTxIn(prevout=outpoint, nSequence=sequence)
vin.append(inp)
for o in outs:
if "script" in o:
sPK = o["script"]
else:
# note the to_scriptPubKey method is only available for standard
# address types
sPK = CCoinAddress(o["address"]).to_scriptPubKey()
out = CMutableTxOut(o["value"], sPK)
vout.append(out)
return CMutableTransaction(vin, vout, nLockTime=locktime, nVersion=version)
def make_shuffled_tx(ins, outs, version=1, locktime=0):
""" Simple wrapper to ensure transaction
inputs and outputs are randomly ordered.
NB: This mutates ordering of `ins` and `outs`.
"""
random.shuffle(ins)
random.shuffle(outs)
return mktx(ins, outs, version=version, locktime=locktime)
def verify_tx_input(tx, i, scriptSig, scriptPubKey, amount=None, witness=None):
flags = set([SCRIPT_VERIFY_STRICTENC])
if witness:
# https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252
flags.add(SCRIPT_VERIFY_P2SH)
flags.add(SCRIPT_VERIFY_WITNESS)
try:
VerifyScript(scriptSig, scriptPubKey, tx, i,
flags=flags, amount=amount, witness=witness)
except ValidationError as e:
return False
return True
def extract_witness(tx, i):
"""Given `tx` of type CTransaction, extract,
as a list of objects of type CScript, which constitute the
witness at the index i, followed by "success".
If the witness is not present for this index, (None, "errmsg")
is returned.
Callers must distinguish the case 'tx is unsigned' from the
case 'input is not type segwit' externally.
"""
assert isinstance(tx, CTransaction)
assert i >= 0
if not tx.has_witness():
return None, "Tx witness not present"
if len(tx.vin) < i:
return None, "invalid input index"
witness = tx.wit.vtxinwit[i]
return (witness, "success")
def extract_pubkey_from_witness(tx, i):
""" Extract the pubkey used to sign at index i,
in CTransaction tx, assuming it is of type p2wpkh
(including wrapped segwit version).
Returns (pubkey, "success") or (None, "errmsg").
"""
witness, msg = extract_witness(tx, i)
sWitness = [a for a in iter(witness.scriptWitness)]
if not sWitness:
return None, msg
else:
if len(sWitness) != 2:
return None, "invalid witness for p2wpkh."
if not is_valid_pubkey(sWitness[1], True):
return None, "invalid pubkey in witness"
return sWitness[1], "success"
def get_equal_outs(tx):
""" If 2 or more transaction outputs have the same
bitcoin value, return then as a list of CTxOuts.
If there is not exactly one equal output size, return False.
"""
retval = []
l = [x.nValue for x in tx.vout]
eos = [i for i in l if l.count(i)>=2]
if len(eos) > 0:
eos = set(eos)
if len(eos) > 1:
return False
for i, vout in enumerate(tx.vout):
if vout.nValue == list(eos)[0]:
retval.append((i, vout))
assert len(retval) > 1
return retval
def is_jm_tx(tx, min_cj_amount=75000, min_participants=3):
""" Identify Joinmarket-patterned transactions.
TODO: this should be in another module.
Given a CBitcoinTransaction tx, check:
nins >= number of coinjoin outs (equal sized)
non-equal outs = coinjoin outs or coinjoin outs -1
at least 3 coinjoin outs (2 technically possible but excluded)
also possible to try to get clever about fees, but won't bother.
note: BlockSci's algo additionally addresses subset sum, so will
give better quality data, but this is kept simple for now.
We filter out joins with less than 3 participants as they are
not really in Joinmarket "correct usage" and there will be a lot
of false positives.
We filter out "joins" less than 75000 sats as they are unlikely to
be Joinmarket and there tend to be many low-value false positives.
Returns:
(False, None) for non-matches
(coinjoin amount, number of participants) for matches.
"""
def assumed_cj_out_num(nout):
"""Return the value ceil(nout/2)
"""
x = nout//2
if nout %2: return x+1
return x
def most_common_value(x):
return max(set(x), key=x.count)
assumed_coinjoin_outs = assumed_cj_out_num(len(tx.vout))
if assumed_coinjoin_outs < min_participants:
return (False, None)
if len(tx.vin) < assumed_coinjoin_outs:
return (False, None)
outvals = [x.nValue for x in tx.vout]
# it's not possible for the coinjoin out to not be
# the most common value:
mcov = most_common_value(outvals)
if mcov < min_cj_amount:
return (False, None)
cjoutvals = [x for x in outvals if x == mcov]
if len(cjoutvals) != assumed_coinjoin_outs:
return (False, None)
# number of participants is the number of assumed
# coinjoin outputs:
return (mcov, assumed_coinjoin_outs)