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.
 
 
 
 

267 lines
10 KiB

#!/usr/bin/python
from past.builtins import basestring
from io import BytesIO
import binascii
import copy
import re
import os
import struct
# note, only used for non-cryptographic randomness:
import random
from jmbitcoin.secp256k1_main import *
from bitcointx.core import (CMutableTransaction, Hash160, CTxInWitness,
CTxWitness, CMutableOutPoint, CMutableTxIn,
CMutableTxOut, ValidationError, lx, x)
from bitcointx.core.script import *
from bitcointx.wallet import P2WPKHBitcoinAddress, CCoinAddress
from bitcointx.core.scripteval import (VerifyScript, SCRIPT_VERIFY_WITNESS,
SCRIPT_VERIFY_P2SH, SIGVERSION_WITNESS_V0)
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.
"""
if not is_valid_pubkey(pub, require_compressed=require_compressed):
raise Exception("Invalid pubkey")
return CScript([OP_DUP, OP_HASH160, Hash160(pub),
OP_EQUALVERIFY, OP_CHECKSIG])
def pubkey_to_p2wpkh_script(pub):
"""
Given a pubkey in bytes (compressed), return a CScript
representing the corresponding pay-to-witness-pubkey-hash
scriptPubKey.
"""
if not is_valid_pubkey(pub, True):
raise Exception("Invalid pubkey")
return CScript([OP_0, Hash160(pub)])
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):
return P2WSH_PRE + bin_sha256(binascii.unhexlify(redeem_script))
def redeem_script_to_p2wsh_address(redeem_script, vbyte, witver=0):
return script_to_address(redeem_script_to_p2wsh_script(redeem_script), vbyte, witver)
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, usehex, require_compressed=True):
raise ValueError("not a valid public key")
scr = [locktime, btc.OP_CHECKLOCKTIMEVERIFY, btc.OP_DROP, pub,
btc.OP_CHECKSIG]
return binascii.hexlify(serialize_script(scr)).decode()
def mk_burn_script(data):
if not isinstance(data, bytes):
raise TypeError("data must be in bytes")
data = binascii.hexlify(data).decode()
scr = [btc.OP_RETURN, data]
return serialize_script(scr)
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 three scriptPubKey types supported: p2pkh, p2wpkh, p2sh-p2wpkh.
Note that signing multisig must be done outside this function, using
the wrapped library.
Returns: (signature, "signing succeeded")
or: (None, errormsg) in case of failure
"""
# script verification flags
flags = set()
def return_err(e):
return None, "Error in signing: " + repr(e)
assert isinstance(tx, CMutableTransaction)
# using direct local access to libsecp256k1 binding, because
# python-bitcoinlib uses OpenSSL key management:
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.
# see line 1256 of bitcointx.core.scripteval.py:
flags.add(SCRIPT_VERIFY_P2SH)
input_scriptPubKey = pubkey_to_p2wpkh_script(pub)
# only created for convenience access to scriptCode:
input_address = P2WPKHBitcoinAddress.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 native:
flags.add(SCRIPT_VERIFY_WITNESS)
else:
tx.vin[i].scriptSig = CScript([input_scriptPubKey])
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 apply_freeze_signature(tx, i, redeem_script, sig):
if isinstance(redeem_script, str):
redeem_script = binascii.unhexlify(redeem_script)
if isinstance(sig, str):
sig = binascii.unhexlify(sig)
txobj = deserialize(tx)
txobj["ins"][i]["script"] = ""
txobj["ins"][i]["txinwitness"] = [sig, redeem_script]
return serialize(txobj)
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),
"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:
# note the to_scriptPubKey method is only available for standard
# address types
out = CMutableTxOut(o["value"],
CCoinAddress(o["address"]).to_scriptPubKey())
vout.append(out)
return CMutableTransaction(vin, vout, nLockTime=locktime)
def make_shuffled_tx(ins, outs, version=1, locktime=0):
""" Simple wrapper to ensure transaction
inputs and outputs are randomly ordered.
Can possibly be replaced by BIP69 in future
"""
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, native=False):
flags = set()
if witness:
flags.add(SCRIPT_VERIFY_P2SH)
if native:
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