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.
 
 
 
 

434 lines
17 KiB

#!/usr/bin/python
# 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)
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 estimate_tx_size(ins, outs, txtype='p2pkh'):
'''Estimate transaction size.
The txtype field as detailed below is used to distinguish
the type, but there is at least one source of meaningful roughness:
we assume the output types are the same as the input (to be fair,
outputs only contribute a little to the overall total). This combined
with a few bytes variation in signature sizes means we will expect,
say, 10% inaccuracy here.
Assuming p2pkh:
out: 8+1+3+2+20=34, in: 1+32+4+1+1+~73+1+1+33=147,
ver:4,seq:4, +2 (len in,out)
total ~= 34*len_out + 147*len_in + 10 (sig sizes vary slightly)
Assuming p2sh M of N multisig:
"ins" must contain M, N so ins= (numins, M, N) (crude assuming all same)
74*M + 34*N + 45 per input, so total ins ~ len_ins * (45+74M+34N)
so total ~ 34*len_out + (45+74M+34N)*len_in + 10
Assuming p2sh-p2wpkh:
witness are roughly 3+~73+33 for each input
(txid, vin, 4+20 for witness program encoded as scriptsig, 4 for sequence)
non-witness input fields are roughly 32+4+4+20+4=64, so total becomes
n_in * 64 + 4(ver) + 4(locktime) + n_out*34
Assuming p2wpkh native:
witness as previous case
non-witness loses the 24 witnessprogram, replaced with 1 zero,
in the scriptSig, so becomes:
n_in * 41 + 4(ver) + 4(locktime) +2 (len in, out) + n_out*34
'''
if txtype == 'p2pkh':
return 10 + ins * 147 + 34 * outs
elif txtype == 'p2sh-p2wpkh':
#return the estimate for the witness and non-witness
#portions of the transaction, assuming that all the inputs
#are of segwit type p2sh-p2wpkh
# Note as of Jan19: this misses 2 bytes (trivial) for len in, out
# and also overestimates output size by 2 bytes.
witness_estimate = ins*109
non_witness_estimate = 4 + 4 + outs*34 + ins*64
return (witness_estimate, non_witness_estimate)
elif txtype == 'p2wpkh':
witness_estimate = ins*109
non_witness_estimate = 4 + 4 + 2 + outs*31 + ins*41
return (witness_estimate, non_witness_estimate)
elif txtype == 'p2shMofN':
ins, M, N = ins
return 10 + (45 + 74*M + 34*N) * ins + 34 * outs
else:
raise NotImplementedError("Transaction size estimation not" +
"yet implemented for type: " + txtype)
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")
usehex = False
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([btc.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)