def test_segwit_create_tx(self): from pycoin.tx.tx_utils import create_tx, sign_tx from pycoin.tx.Spendable import Spendable from pycoin.tx.pay_to.ScriptPayToAddress import ScriptPayToAddress from pycoin.tx.pay_to.ScriptPayToAddressWit import ScriptPayToAddressWit from pycoin.tx.pay_to.ScriptPayToScriptWit import ScriptPayToScriptWit from pycoin.ui import address_for_pay_to_script_wit, script_obj_from_address key1 = Key(1) coin_value = 5000000 script = ScriptPayToAddressWit(b'\0', key1.hash160()).script() tx_hash = b'\ee' * 32 tx_out_index = 0 spendable = Spendable(coin_value, script, tx_hash, tx_out_index) key2 = Key(2) tx = create_tx([spendable], [(key2.address(), coin_value)]) self.check_unsigned(tx) sign_tx(tx, [key1.wif()]) self.check_signed(tx) self.assertEqual(len(tx.txs_in[0].witness), 2) s1 = ScriptPayToAddress(key1.hash160()).script() address = address_for_pay_to_script_wit(s1) spendable.script = script_obj_from_address(address).script() tx = create_tx([spendable], [(key2.address(), coin_value)]) self.check_unsigned(tx) sign_tx(tx, [key1.wif()], p2sh_lookup=build_p2sh_lookup([s1])) self.check_signed(tx)
def txs_from_json(path): """ Read tests from ./data/tx_??valid.json Format is an array of arrays Inner arrays are either [ "comment" ] or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...], serializedTransaction, verifyFlags] ... where all scripts are stringified scripts. verifyFlags is a comma separated list of script verification flags to apply, or "NONE" """ with open(path, 'r') as f: for tvec in json.load(f): if len(tvec) == 1: continue assert len(tvec) == 3 prevouts = tvec[0] for prevout in prevouts: assert len(prevout) == 3 tx_hex = tvec[1] flags = set() for flag in tvec[2].split(','): assert flag in FLAGS flags.add(flag) try: tx = Tx.from_hex(tx_hex) except: print("Cannot parse tx_hex: %s" % tx_hex) raise spendable_db = {} blank_spendable = Spendable(0, b'', b'\0' * 32, 0) for prevout in prevouts: spendable = Spendable(coin_value=1000000, script=compile_script(prevout[2]), tx_hash=h2b_rev(prevout[0]), tx_out_index=prevout[1]) spendable_db[(spendable.tx_hash, spendable.tx_out_index)] = spendable unspents = [ spendable_db.get((tx_in.previous_hash, tx_in.previous_index), blank_spendable) for tx_in in tx.txs_in ] tx.set_unspents(unspents) yield (tx, flags)
def test_simple_spend(self): FEE = 10000 # create a fake Spendable COIN_VALUE = 100000000 spendables = [Spendable(COIN_VALUE, standard_tx_out_script(BITCOIN_ADDRESSES[0]), FAKE_HASHES[1], 0)] EXPECTED_IDS = [ "d28bff6c4a8a0f9e7d5b7df0670d07b43c5613d8c9b14e84707b1e2c0154a978", "7afbe63b00171b18f806ebd48190ebc1c68cadf286a85489c06ebe43d146489e", "2b90c150ba1d080a0816952f5d9c2642d408989cbc4d4c540591c8c9241294bd", "17b0b5b22887081595c1a9ad153e903f63bb8682ae59d6082df018dc617e5e67", "dff1b34c243becb096ad2a2d6119973067a8137cc8bf95615e742bbf6f0944c1", "206bbfbb759a8f91901d86b62390d7587f6097a32994ece7752d143fc8a02cee", "7841412716ad35cbc9954e547ba85be89e5ed0b34ed5fb8d7594517318dc10d6", "8b7e643bf47db46ada7a75b8498990b111fe20917b5610ca6759b8b0078ccd5e", "5756f0a6d5a2bbb93a07f0729d3773aaafd21393ede3ec0e20b0b5219ca45548", "32dcbb34965ea72d2caa59eb1e907aa28bac2afea43214c1809f5d8ed360f30e", ] for count in range(1, 11): tx = create_signed_tx(spendables, BITCOIN_ADDRESSES[1:count+1], wifs=WIFS[:1]) self.assertEqual(tx.bad_signature_count(), 0) self.assertEqual(tx.fee(), FEE) self.assertEqual(tx.id(), EXPECTED_IDS[count-1]) # TODO: add check that s + s < generator for each signature for i in range(count): extra = (1 if i < ((COIN_VALUE - FEE) % count) else 0) self.assertEqual(tx.txs_out[i].coin_value, (COIN_VALUE - FEE)//count + extra)
def get_spendables(self, address: str, amount: int = None, get_all=False): spendables = [] # Safety for some very good code. if not amount and not get_all and amount <= 0: raise Exception("Amount or get_all flag must be given.") # Check for unspents. unspents = self.connection.listunspent(0, 9999999, [address]) if not unspents: raise Exception(f"No unspent inputs found on adddress {address}.") for tx in unspents: spendables.append( Spendable.from_dict( dict(coin_value=tx["amount"] * 1000000, script_hex="0000", tx_hash_hex=tx["txid"], tx_out_index=tx["vout"]))) if not get_all and (amount < 0): break if amount: amount -= tx["amount"] * 1000000 return spendables
def createTX(payment): (content,(addrToSend,amountToSend)) = payment if content is None: pass # it should include a reason for the error.... # raise an exception return None # actually instead of returning it should raise an exception.... else: (utxos,retAddr) = content keys = [] spendings = [] for ((txid,index),amount,key) in utxos: keys += [key.wif()] spendings += [ Spendable( amount , ScriptPayToAddress( key.hash160()).script() , h2b_rev(txid) , index ) ] print "########################################################################################" print ('spendings :',spendings) print ('addr to send :',addrToSend) print ('amountToSend :',amountToSend) print ('retAddr :',retAddr) print ('wif list :',keys) print ('transaction :',create_signed_tx(spendings,[(addrToSend,amountToSend),retAddr],wifs=keys, fee="standard").as_hex()) print "########################################################################################"
def test_confirm_input(self): FEE = 10000 # create a fake Spendable COIN_VALUE = 100000000 spendables = [ Spendable(COIN_VALUE, standard_tx_out_script(BITCOIN_ADDRESSES[0]), FAKE_HASHES[1], 0) ] tx_1 = create_signed_tx(spendables, BITCOIN_ADDRESSES[1:2], wifs=WIFS[:1]) spendables = tx_1.tx_outs_as_spendable() tx_db = dict((tx.hash(), tx) for tx in [tx_1]) tx_2 = create_signed_tx(spendables, BITCOIN_ADDRESSES[2:3], wifs=WIFS[:3]) tx_2.validate_unspents(tx_db) tx_2 = create_signed_tx([s.as_dict() for s in spendables], BITCOIN_ADDRESSES[2:3], wifs=WIFS[:3]) tx_2.validate_unspents(tx_db) tx_2 = create_signed_tx([s.as_text() for s in spendables], BITCOIN_ADDRESSES[2:3], wifs=WIFS[:3]) tx_2.validate_unspents(tx_db)
def test_create_trx(self): SelectParams('testnet') b = h2b('8443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b154') tx_input = Spendable(200, b'18eKkAWyU9kvRNHPKxnZb6wwtPMrNmRRRA', b, 0) tx_outs = [tx_utils.create_transaction_output('mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', 0.0000275)] tx = tx_utils.create_trx('TEST'.encode('utf-8'), 3, 'mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', tx_outs, [tx_input]) hextx = b2h(tx.serialize()) self.assertEquals(hextx, '01000000018443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b1540000000000ffffffff0300000000000000001976a914072a22e5913cd939904c46bbd0bc56755543384b88acc5000000000000001976a914072a22e5913cd939904c46bbd0bc56755543384b88ac0000000000000000066a045445535400000000')
async def get_spendable_list_for_addr(self, addr: str) -> List[Spendable]: unspent_list = await self.get_listunspent_for_addr(addr) return [ Spendable( unspent['amount'], bytes(unspent['scriptPubKey']), unspent['outpoint'].hash, unspent['outpoint'].n, ) for unspent in unspent_list ]
def test_verify_transaction(self): SelectParams('testnet') b = h2b('8443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b154') tx_input = Spendable(200, b'18eKkAWyU9kvRNHPKxnZb6wwtPMrNmRRRA', b, 0) tx_outs = [tx_utils.create_transaction_output('mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', 0.0000275)] op_return_val = h2b('e9cee71ab932fde863338d08be4de9dfe39ea049bdafb342ce659ec5450b69ae') tx = tx_utils.create_trx(op_return_val, 3, 'mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', tx_outs, [tx_input]) hextx = b2h(tx.serialize()) tx_utils.verify_transaction(hextx, b2h(op_return_val))
def test_BloomFilter(self): bf = BloomFilter(20, hash_function_count=5, tweak=127) bf.add_hash160(h2b("751e76e8199196d454941c45d1b3a323f1433bd6")) tx_hash = h2b( "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") spendable = Spendable(coin_value=1000, script=b'foo', tx_hash=tx_hash, tx_out_index=1) bf.add_spendable(spendable) self.assertEqual(bf.filter_bytes, h2b("0000400000000000090030400042100100000000"))
def spend_sh_fund(tx_ins, wif_keys, tx_outs): """ spend script hash fund the key point of an input comes from multisig address is that, its sign script is combined with several individual signs :param tx_ins: list with tuple(tx_id, idx, balance, address, redeem_script) :param wif_keys: private keys in wif format, technical should be the same order with the pubkey in redeem script, but pycoin has inner control, so here order is not mandatory :param tx_outs: balance, receiver_address :return: raw hex and tx id """ _txs_in = [] _un_spent = [] for tx_id, idx, balance, address, _ in tx_ins: # must h2b_rev NOT h2b tx_id_b = h2b_rev(tx_id) _txs_in.append(TxIn(tx_id_b, idx)) _un_spent.append( Spendable( balance, script_obj_from_address(address, netcodes=[NET_CODE]).script(), tx_id_b, idx)) _txs_out = [] for balance, receiver_address in tx_outs: _txs_out.append( TxOut( balance, script_obj_from_address(receiver_address, netcodes=[NET_CODE]).script())) version, lock_time = 1, 0 tx = Tx(version, _txs_in, _txs_out, lock_time) tx.set_unspents(_un_spent) # construct hash160_lookup[hash160] = (secret_exponent, public_pair, compressed) for each individual key hash160_lookup = build_hash160_lookup( [Key.from_text(wif_key).secret_exponent() for wif_key in wif_keys]) for i in range(0, len(tx_ins)): # you can add some conditions that if the input script is not p2sh type, not provide p2sh_lookup, # so that all kinds of inputs can work together p2sh_lookup = build_p2sh_lookup([binascii.unhexlify(tx_ins[i][-1])]) tx.sign_tx_in(hash160_lookup, i, tx.unspents[i].script, hash_type=SIGHASH_ALL, p2sh_lookup=p2sh_lookup) return tx.as_hex(), tx.id()
def test_confirm_input_raises(self): FEE = 10000 # create a fake Spendable COIN_VALUE = 100000000 spendables = [Spendable(COIN_VALUE, standard_tx_out_script(BITCOIN_ADDRESSES[0]), FAKE_HASHES[1], 0)] tx_1 = create_signed_tx(spendables, BITCOIN_ADDRESSES[1:2], wifs=WIFS[:1]) spendables = tx_1.tx_outs_as_spendable() spendables[0].coin_value += 100 tx_db = dict((tx.hash(), tx) for tx in [tx_1]) tx_2 = create_signed_tx(spendables, BITCOIN_ADDRESSES[2:3], wifs=WIFS[:3]) self.assertRaises(BadSpendableError, tx_2.validate_unspents, tx_db)
def test_p2sh_multisig_sequential_signing(self): raw_scripts = [ h2b("52210234abcffd2e80ad01c2ec0276ad02682808169c6fafdd25ebfb60703df272b461" "2102e5baaafff8094e4d77ce8b009d5ebc3de9110085ebd3d96e50cc7ce70faf175221" "0316ee25e80eb6e6fc734d9c86fa580cbb9c4bfd94a19f0373a22353ececd4db6853ae" ) ] spendable = { 'script_hex': 'a914c4ed4de526461e3efbb79c8b688a6f9282c0464687', 'does_seem_spent': 0, 'block_index_spent': 0, 'coin_value': 10000, 'block_index_available': 0, 'tx_out_index': 0, 'tx_hash_hex': '0ca152ba6b88db87a7ef1afd24554102aca1ab86cf2c10ccbc374472145dc943' } key_1 = 'Kz6pytJCigYHeMsGLmfHQPJhN5og2wpeSVrU43xWwgHLCAvpsprh' key_2 = 'Kz7NHgX7MBySA3RSKj9GexUSN6NepEDoPNugSPr5absRDoKgn2dT' for ordered_keys in [(key_1, key_2), (key_2, key_1)]: txs_in = [ TxIn(previous_hash=h2b( '43c95d14724437bccc102ccf86aba1ac02415524fd1aefa787db886bba52a10c' ), previous_index=0) ] txs_out = [ TxOut(10000, script_for_address('3KeGeLFmsbmbVdeMLrWp7WYKcA3tdsB4AR')) ] unspents = [Spendable.from_dict(spendable)] tx = Tx(version=DEFAULT_VERSION, txs_in=txs_in, txs_out=txs_out, unspents=unspents) for key in ordered_keys: self.assertEqual(tx.bad_signature_count(), 1) p2sh_lookup = build_p2sh_lookup(raw_scripts) tx.sign(LazySecretExponentDB([key], {}, [secp256k1_generator]), p2sh_lookup=p2sh_lookup) self.assertEqual(tx.bad_signature_count(), 0)
def spendables_for_address(self, bitcoin_address): """ Return a list of Spendable objects for the given bitcoin address. """ URL = "https://blockchain.info/unspent?active=%s" % bitcoin_address r = json.loads(urlopen(URL).read().decode("utf8")) spendables = [] for u in r["unspent_outputs"]: coin_value = u["value"] script = h2b(u["script"]) previous_hash = h2b(u["tx_hash"]) previous_index = u["tx_output_n"] spendables.append( Spendable(coin_value, script, previous_hash, previous_index)) return spendables
def createTransaction(self, address, amount, keychain, fee="standard", confirms=0): unspents_result = yield self.unspents() spendables = [] p2sh = [] chain_paths = [] # Strip leading / keychain_path = keychain['path'][1:] for unspent in unspents_result["unspents"]: if unspent["confirmations"] < confirms: continue p2sh.append(h2b(unspent["redeemScript"])) spendable = Spendable(unspent["value"], h2b(unspent["script"]), h2b_rev(unspent["tx_hash"]), unspent["tx_output_n"]) spendables.append(spendable) chain_paths.append(keychain_path + unspent['chainPath']) p2sh_lookup = build_p2sh_lookup(p2sh) address_result = yield self.createAddress(1) change = address_result["address"] tx = tx_utils.create_tx(spendables, [(address, amount), change], fee) # address_keys = [BIP32Node.from_hwif(keychain["xprv"]).subkey_for_path("0/0/0/0"), # BIP32Node.from_hwif(keychain["xprv"]).subkey_for_path(address_result['path'])] spendable_keys = [ BIP32Node.from_hwif(keychain["xprv"]).subkey_for_path(path) for path in chain_paths ] # all_keys = address_keys + spendable_keys hash160_lookup = build_hash160_lookup( [key.secret_exponent() for key in spendable_keys]) pprint(tx) tx.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) pprint(tx) returnValue({'tx': tx.as_hex(), 'fee': tx.fee()})
def spendables_for_address(self, address): """ Return a list of Spendable objects for the given bitcoin address. """ logging.debug('spendables_for_address %s', address) spendables = [] url_append = "?unspentOnly=true&token=%s&includeScript=true" % self.api_token url = self.base_url + '/addrs/' + address + url_append result = json.loads(urlopen(url).read().decode("utf8")) for txn in result.get("txrefs", []): coin_value = txn.get("value") script = h2b(txn.get("script")) previous_hash = h2b_rev(txn.get("tx_hash")) previous_index = txn.get("tx_output_n") spendables.append( Spendable(coin_value, script, previous_hash, previous_index)) return spendables
def spendables_for_address(self, address): """ Converts to pycoin Spendable type :param address: :return: list of Spendables """ unspent_outputs = bitcoin.rpc.Proxy().listunspent(addrs=[address]) logging.debug('spendables_for_address %s', address) spendables = [] for unspent in unspent_outputs: coin_value = unspent.get('amount', 0) outpoint = unspent.get('outpoint') script = unspent.get('scriptPubKey') previous_hash = outpoint.hash previous_index = outpoint.n spendables.append(Spendable(coin_value, script, previous_hash, previous_index)) return spendables
def tether_tx(tx_ins, private_key, send_amount, receiver): """ simple usdt transaction here assume utxo comes from the sender and is used for mine fee bitcoin change will be sent back to sender address of course different address's utxo can be used for mine fee, but should be aware sender is determined by the first input in the tx bitcoin change can also be sent back to different address, but should be aware receiver is indicated by the last output address that is not the sender for full customization, use btc sample p2pkh_tx :param tx_ins: utxo from the sender :param private_key: private key of the same sender :param send_amount: (display amount) * (10 ** 8) :param receiver: address to receive usdt """ _txs_in = [] _un_spent = [] total_bal = 0 for tx_id, idx, balance, address in tx_ins: total_bal += balance # must h2b_rev NOT h2b tx_id_b = h2b_rev(tx_id) _txs_in.append(TxIn(tx_id_b, idx)) _un_spent.append(Spendable(balance, standard_tx_out_script(address), tx_id_b, idx)) txn_fee = estimate_p2pkh_tx_bytes(len(tx_ins), 3) * recommend_satoshi_per_byte() _txs_out = [TxOut(total_bal - txn_fee - 546, standard_tx_out_script(tx_ins[0][3])), TxOut(0, binascii.unhexlify(omni_tether_script(send_amount))), TxOut(546, standard_tx_out_script(receiver))] version, lock_time = 1, 0 tx = Tx(version, _txs_in, _txs_out, lock_time) tx.set_unspents(_un_spent) solver = build_hash160_lookup([int(private_key, 16)] * len(tx_ins)) signed_tx = tx.sign(solver, hash_type=SIGHASH_ALL) return signed_tx.as_hex(), signed_tx.id()
def spendables_for_address(self, address): """ Return a list of Spendable objects for the given bitcoin address. """ logging.info('trying to get spendables from blockcypher') spendables = [] url_append = '?unspentOnly=true&includeScript=true' if self.api_token: url_append += '&token=' + self.api_token url = self.base_url + '/addrs/' + address + url_append response = requests.get(url) if int(response.status_code) == 200: for txn in response.json().get('txrefs', []): coin_value = txn.get('value') script = h2b(txn.get('script')) previous_hash = h2b_rev(txn.get('tx_hash')) previous_index = txn.get('tx_output_n') spendables.append(Spendable(coin_value, script, previous_hash, previous_index)) return spendables
def _spendables (self, value): r = self._do_request ('listunspent')['result'] random.shuffle (r) sps = [] tot = 0 for s in r: if s['address'] != self.address: continue txid = '' for x in range (len (s['txid']), -2, -2): txid += s['txid'][x:x+2] tot += int (float (s['amount']) * 100000000) sps.append (Spendable.from_dict ({'coin_value': int (float (s['amount']) * 100000000), 'script_hex': s['scriptPubKey'], 'tx_hash_hex': txid, 'tx_out_index': int (s['vout'])})) if tot >= value: return sps return sps
def test_is_invalid(self): for (prevouts, tx_hex, flags) in txs_from_json(TX_INVALID_JSON): try: tx = Tx.from_hex(tx_hex) if not check_transaction(tx): continue unspents = [ Spendable(coin_value=1000000, script=compile_script(prevout[2]), tx_hash=prevout[0], tx_out_index=prevout[1]) for prevout in prevouts ] tx.set_unspents(unspents) bs = tx.bad_signature_count() self.assertEqual(bs, 0) except: continue self.fail("Invalid transaction: " + tx.id() + " appears to be valid.")
def test_p2sh_multisig_sequential_signing(self): raw_scripts = [h2b( "52210234abcffd2e80ad01c2ec0276ad02682808169c6fafdd25ebfb60703df272b461" "2102e5baaafff8094e4d77ce8b009d5ebc3de9110085ebd3d96e50cc7ce70faf175221" "0316ee25e80eb6e6fc734d9c86fa580cbb9c4bfd94a19f0373a22353ececd4db6853ae")] txs_in = [TxIn(previous_hash=h2b('43c95d14724437bccc102ccf86aba1ac02415524fd1aefa787db886bba52a10c'), previous_index=0)] txs_out = [TxOut(10000, standard_tx_out_script('3KeGeLFmsbmbVdeMLrWp7WYKcA3tdsB4AR'))] spendable = {'script_hex': 'a914c4ed4de526461e3efbb79c8b688a6f9282c0464687', 'does_seem_spent': 0, 'block_index_spent': 0, 'coin_value': 10000, 'block_index_available': 0, 'tx_out_index': 0, 'tx_hash_hex': '0ca152ba6b88db87a7ef1afd24554102aca1ab86cf2c10ccbc374472145dc943'} tx__prototype = Tx(version=DEFAULT_VERSION, txs_in=txs_in, txs_out=txs_out, unspents=[Spendable.from_dict(spendable)]) key_1 = 'Kz6pytJCigYHeMsGLmfHQPJhN5og2wpeSVrU43xWwgHLCAvpsprh' key_2 = 'Kz7NHgX7MBySA3RSKj9GexUSN6NepEDoPNugSPr5absRDoKgn2dT' for ordered_keys in [(key_1, key_2), (key_2, key_1)]: tx = copy.deepcopy(tx__prototype) for key in ordered_keys: self.assertEqual(tx.bad_signature_count(), 1) tx.sign(LazySecretExponentDB([key], {}), p2sh_lookup=build_p2sh_lookup(raw_scripts)) self.assertEqual(tx.bad_signature_count(), 0)
def _spendables (self, value): code = self._chaincodeToChainSoName (self.chain) u = 'https://chain.so/api/v2/get_tx_unspent/'+code+'/'+self.address #print (u) while True: try: d = requests.get (u, headers={'content-type': 'application/json'}).json() except: time.sleep (5) continue sps = [] tot = 0 random.shuffle (d['data']['txs']) for s in d['data']['txs']: #if int (s['confirmations']) > 0: txid = s['txid'] #'' #for x in range (len (s['txid']), -2, -2): # txid += s['txid'][x:x+2] if (txid+':'+str (s['output_no'])) in self.lockedspendables: #print ('Locked spendable') continue tot += int (float (s['value']) * 100000000) sps.append (Spendable.from_dict ({'coin_value': int (float (s['value']) * 100000000), 'script_hex': s['script_hex'], 'tx_hash_hex': txid, 'tx_out_index': int (s['output_no'])})) self.lockedspendables.append (txid+':'+str (s['output_no'])) if tot >= value: #print (sps) return sps return sps
def test_is_valid(self): for (prevouts, tx_hex, flags) in txs_from_json(TX_VALID_JSON): try: tx = Tx.from_hex(tx_hex) except: self.fail("Cannot parse tx_hex: " + tx_hex) if not check_transaction(tx): self.fail("check_transaction(tx) = False for valid tx: " + tx_hex) unspents = [ Spendable(coin_value=1000000, script=compile_script(prevout[2]), tx_hash=prevout[0], tx_out_index=prevout[1]) for prevout in prevouts ] tx.set_unspents(unspents) bs = tx.bad_signature_count() if bs > 0: msg = str(tx_hex) + " bad_signature_count() = " + str(bs) self.fail(msg)
def spend_pkh_fund(tx_ins, in_keys, tx_outs): """ p2pkh address send to p2pkh p2sh transaction :param tx_ins: list with tuple(tx_id, idx, balance, address) :param in_keys: list of private keys in hex format corresponding to each input :param tx_outs: balance, receiver_address :return: raw hex and tx id """ _txs_in = [] _un_spent = [] for tx_id, idx, balance, address in tx_ins: # must h2b_rev NOT h2b tx_id_b = h2b_rev(tx_id) _txs_in.append(TxIn(tx_id_b, idx)) _un_spent.append( Spendable( balance, script_obj_from_address(address, netcodes=[NET_CODE]).script(), tx_id_b, idx)) _txs_out = [] for balance, receiver_address in tx_outs: _txs_out.append( TxOut( balance, script_obj_from_address(receiver_address, netcodes=[NET_CODE]).script())) version, lock_time = 1, 0 tx = Tx(version, _txs_in, _txs_out, lock_time) tx.set_unspents(_un_spent) solver = build_hash160_lookup([int(pri_hex, 16) for pri_hex in in_keys]) tx.sign(solver, hash_type=SIGHASH_ALL) return tx.as_hex(), tx.id()
def build_single_utxo_signed_bch_tx(args, wallet_key, destination_address, ga_address_pointer, redeem_script_hex, tx_hash_hex, utxo_index, incoming_satoshis, total_fee_satoshis): '''Builds and signs a single-output Bitcoin Cash (BCH) transaction for a single UTXO. This method prepares a transaction that sends all BCH from a single UTXO (unspent transaction output) held by a 2of2 GreenAddress multisig wallet to a single destination address (minus fees). If you have multiple UTXOs in your GreenAddress wallet (e.g. from multiple deposits to the wallet) you'll need to run the method once for each UTXO and broadcast each resulting TX separately. Very briefly, a UTXO represents some BTC (or BCH) that is currently held by the wallet. The UTXO is defined by the hash of the transaction that created it (tx_hash_hex) and the index of the output in the list of outputs for that transaction (utxo_index). If you're not clear on what "outputs" or UTXOs are, please spend a couple minutes familiarizing yourself with Bitcoin/BCH transaction basics, https://bitcoin.org/en/developer-guide#transactions or other references below, before trying to manually craft these parameters. I've documented them below, but some broader context is going to be helpful. This method is primarily intended for helping recover BCH associated with BTC you had in your GreenAddress wallet at the time of the August 1, 2017 hard fork that created BCH, but should also be usable if you accidentally sent BCH (instead of BTC) to a GreenAddress address post-fork. This method currently does not support: * 2of3 wallets * GreenAddress subaccounts Support for either of these should be manageable to add, so please contact me if it would be useful for you. References: P2SH multisigs: https://bitcoin.org/en/developer-guide#multisig OP_CHECKMULTISIG details: https://bitcoin.org/en/developer-reference#term-op-checkmultisig Opcodes: https://en.bitcoin.it/wiki/Script#Opcodes Detailed multisig TX breakdown (great if you want to parse through your TX or signature script): http://www.soroushjp.com/2014/12/20/bitcoin-multisig-the-hard-way-understanding-raw-multisignature-bitcoin-transactions/ Decode a transaction (does not break down the scripts): https://blockchain.info/decode-tx Glossary entries: https://bitcoin.org/en/developer-guide#transactions https://bitcoin.org/en/glossary/address https://bitcoin.org/en/glossary/signature-script https://bitcoin.org/en/glossary/pubkey-script https://bitcoin.org/en/glossary/public-key https://bitcoin.org/en/glossary/output https://bitcoin.org/en/glossary/unspent-transaction-output My pre-fork and post-fork transactions from which I'm pulling my examples: Pre: https://btc.com/e65e67cef4078f0a44f46bd1740c21e3dc8577c90c71e49fea876cb7bf135b70 Post: https://btc.com/a922ab0a66e66afe2ddb3141d88f8304e79c134e0bce8178823658b88b0e2b7a Args: args: Command-line args. wallet_key (int): GreenAddress wallet key, as returned by bip32_key_from_seed in https://github.com/ElementsProject/libwally-core/blob/master/include/wally_bip32.h destination_address (string): Standard-format BCH address to which you want to send the recovered BCH. Example: 19JRdfanvKzU7d6KvKgGTYar6kzBDba6Jn (author's BCH address -- use your own) These are generally base58check-encoded hashes. See https://bitcoin.org/en/glossary/address ga_address_pointer (int): GreenAddress's internal pointer to the address used by the UTXO (not destination address). This number let's GreenAddress know which keys to use when signing the transaction. You can find this by looking at the output of calls to GreenAddress's addressbook.get_my_addresses API method, and finding the entry that matches your UTXO's address. Other recovery tools pull this from the 'prev_pointers' field in nlocktimes.zip. Example: In my case, GreenAddress returned the following record (among others) when I called get_my_addresses. ga_address: {u'pointer': 15, u'addr_type': u'p2sh', u'num_tx': 2, u'ad': u'3FQBSLAsFF4NMuiEVShMae7Uu9JcVSRQvA'} Since 3FQBSLAsFF4NMuiEVShMae7Uu9JcVSRQvA was the address holding my UTXO, I'd set ga_address_pointer = 15. redeem_script_hex (string): Hex string encoding of the full redeem script for the UTXO (unspent transaction) you're trying to spend. Since we're extracting from a 2of2 P2SH multisig wallet (GreenAddress default), this string should start with '52' (the OP_2 opcode that represents the first '2' in '2of2'), and end with '52ae' (the OP_2 opcode that represents the second '2' in '2of2' and the OP_CHECKMULTISIG opcode). The middle contains the two Pubkeys for which signatures are needed in order to spend the UTXO. The string should be roughly 142 hex characters long, but could potentially vary a bit if GreenAddress employs pubkeys of different lengths. The redeem script will form the tail of the input script in the final transaction, and will be exactly the same for the BCH transaction as for the BTC transaction. The rest of the signature script will differ, since the signatures depend on other pieces of the transaction as well. The redeem script for a UTXO is generally not public. There are roughly three ways to get it in our case: 1) Extract it from the Input Script used by a BTC transaction that spent your UTXO (BTC, not BCH. As noted, it starts with '52' and ends with '52ae', and is probably just the last 142 hex characters of the input script. If you use the advanced view on the blockchain.info viewer, this will be the last long string under the relevant ScriptSig. See this example link, which matches up with the example redeemscript hex given below. https://blockchain.info/tx/e65e67cef4078f0a44f46bd1740c21e3dc8577c90c71e49fea876cb7bf135b70?show_adv=true 2) Extract it from the nlocktimes.zip generated when you initiated the transaction holding the UTXO. For recovery, this would be your last pre-fork nlocktimes.zip, though if you already have it, you may want to instead just use one of the recovery tools that depends on nlocktimes.zip, such as https://github.com/dumpyourbcash/garecovery 3) Figure out which pointer GreenAddress is supposed to use next, and determine both public keys from that. I haven't done this, but it shouldn't be necessary in any case. If the pre-fork BTC funds have already been spent, you can do (1). If they haven't, you can do (2), since GreenAddress lets you re-request the "latest" nlocktimes through the wallet. Example: 5221036a08f0f8a665da604b3b4e1330ac7172e1e5a9a3466c95fc79bb50fe39eba8a22103649737c9a453eb7efa99c106117614c90ca96211542fea013ae5fda8d913bbb252ae tx_hash_hex (string): Hex string encoding of the transaction hash for the transaction containing the UTXO you're trying to spend. Example: e65e67cef4078f0a44f46bd1740c21e3dc8577c90c71e49fea876cb7bf135b70 utxo_index (int): Index (0-based) of the unspent transaction output in the list of outputs in the transaction you're trying to spend. Example: My pre-fork transaction had two outputs, in order: 0: 24870700 satoshis to 12HdJmPZPNo6hyYa224aUPPw1j7fhruemZ (address of person I was paying) 1: 18232197 satoshis to 3FQBSLAsFF4NMuiEVShMae7Uu9JcVSRQvA (new multisig address where GA put my balance) Since my leftovers address was second, I'd set utxo_index = 1. Had it been first, I'd set utxo_index = 0. incoming_satoshis (int): Number of satoshis (BCH * 10^8) you want to spend from the UTXO. This should generally be the full amount in the UTXO if it's less, you'll be implicitly giving all the leftovers to the miners as a fee. Example: 18232197 in my case (0.18 BCH) total_fee_satoshis (int): The total number of satoshis you want to pay to the miners as a fee. Divide total_fee_satoshis by size of transaction (~350 bytes) to get the fee in satoshis per byte. See current fees by looking at recent transactions or sites like https://bitcointicker.co/bccnetworkstats/, though beware the units may differ. I've set the default to 30000 (0.0003 BCH), which is unnecessarily high (~100 satoshis per byte), to be safe and to ensure the transaction get processed immediately, and because it's what I used. Returns: pycoin.tx.Tx: The fully signed transaction object. ''' # The UTXO you want to spend. spendable = Spendable.from_dict({ "coin_value": incoming_satoshis, "script_hex": redeem_script_hex, "tx_hash_hex": tx_hash_hex, "tx_out_index": utxo_index }) tx = pycoin.tx.tx_utils.create_tx(spendables=[spendable], payables=[destination_address], fee=total_fee_satoshis) logging.info("raw unsigned tx: %s", tx.as_hex()) # SIGNING # User Signature redeem_script = spendable.script # The sighash of the transaction, which is the thing that will be signed independently by the wallet and # GreenAddress (via their API call). The hash is independent of the TX input signature scripts, since those will # end up including the signatures themselves. tx_in_sighash = tx_segwit_hash(tx, 0, redeem_script, incoming_satoshis) # The user signature depends only on the private key (derived from the user's wallet mnemonic) and the sighash # (derived from the transaction). private_key = bip32_key_get_priv_key( bip32_key_from_parent_path(wallet_key, [1, ga_address_pointer], BIP32_FLAG_SKIP_HASH)) user_signature = ec_sig_to_der( ec_sig_from_bytes(private_key, tx_in_sighash, EC_FLAG_ECDSA)) + bytearray([ 0x41, ]) # Updating the script at this point would not be necessary, except that the GreenAddress API method has expectations # for what the input script should look like when it signs the transaction (likely because it wants to be able to # return an updated version of the script, which we ignore anyway). The GreenAddress signature itself will not # depend on the script set here, only on the redeem script, which is passed to the API method separately. # If this isn't done, the API method will fail with 'Invalid signature placeholder'. tx.txs_in[0].script = inscript.prep_for_vault_sign_alt_tx( redeem_script, user_signature) # GreenAddress Signature twofactor = request_twofactor_if_needed(tx) vault_sign_inputs = [{ "value": incoming_satoshis, "script": redeem_script_hex, "subaccount": None, # This recovery tool currently does not support subaccounts. "pointer": ga_address_pointer, }] # GreenAddress needs the tx.as_hex so that it can compute the hash and return an updated input script along with # the signature, but we're only interested in the signature, since we're rebuilding the input script below anyway. ga_signatures = args.conn.call("vault.sign_alt_tx", tx.as_hex(), "bcash", vault_sign_inputs, twofactor) # Signature order matters, and is determined by the order of the pubkeys used when creating the redeem script, and # thus the hash that forms the pubkey script stored in the UTXO. Since we have the redeem script, we could check the # order that the pubkeys appear in the redeem script, and make sure the signatures (derived from the corresponding # private keys) appear in the same order. However, for now we're assuming that GreenAddress always puts their # signature first, which means it should appear first in the signatures list below. signatures = [hex_to_bytes(ga_signatures['signatures'][0]), user_signature] tx.txs_in[0].script = inscript.multisig(redeem_script, signatures) logging.warning("fully signed tx: " + tx.as_hex()) return tx
def searchSpendById(id): c = CoinSqlite3()._exec_sql( 'Select * from TransactionInfoOut where id = ? ', id) tmp = c.fetchone() spend = Spendable(tmp[1], tmp[2], tmp[4], tmp[6]) return spend
def send(self, wallet_id, passcode, address, amount, message='', fee=10000): """ Send bitcoins to address :param wallet_id: bitgo wallet id :param address: bitcoin address :param amount: btc amount in satoshis :return: boolean """ wallet = self.get_wallet(wallet_id) if not wallet['spendingAccount']: raise NotSpendableWallet() if not wallet['isActive']: raise NotActiveWallet() if amount < 10000: raise Exception('amount to small') if wallet['confirmedBalance'] < amount: raise NotEnoughFunds('Not enough funds: balance %s amount %s' % (wallet['confirmedBalance'], amount)) change_address = self.create_address(wallet_id, chain=1) usableKeychain = False spendables = [] chain_paths = [] p2sh = [] payables = [(address, amount)] keychain_path = "" for keychain in wallet['private']['keychains']: keychain_path = keychain['path'][1:] keychain = self.get_keychain(keychain['xpub']) if 'encryptedXprv' not in keychain: continue usableKeychain = True break if not usableKeychain: raise BitGoError("didn't found a spendable keychain") data = json.loads(keychain['encryptedXprv']) #add base64 paddings for k in ['iv', 'salt', 'ct']: data[k] = data[k] + "==" cipher = sjcl.SJCL() xprv = cipher.decrypt(data, passcode) unspents = self.get_unspents(wallet_id) total_value = 0 for d in unspents['unspents'][::-1]: path = keychain_path + d['chainPath'] chain_paths.append(path) p2sh.append(h2b(d["redeemScript"])) spendables.append( Spendable(d["value"], h2b(d["script"]), h2b_rev(d["tx_hash"]), d["tx_output_n"])) total_value += d['value'] if total_value > amount: break if total_value > (amount + fee): #add a change address #TODO: create address payables.append(change_address) p2sh_lookup = build_p2sh_lookup(p2sh) spendable_keys = [] priv_key = BIP32Node.from_hwif(xprv) spendable_keys = [ priv_key.subkey_for_path(path) for path in chain_paths ] hash160_lookup = build_hash160_lookup( [key.secret_exponent() for key in spendable_keys]) tx = create_tx(spendables, payables) tx.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) r = requests.post(self.url + '/tx/send', { 'tx': tx.as_hex(), 'message': message }, headers={ 'Authorization': 'Bearer %s' % self.access_token, }) return r.json()
def test_spendable(spendable_small_hex_small_vout): spendable = Spendable.from_tx_out(TxOut(0, b"\x00"), b"\x00", 0) assert isinstance(spendable, Spendable) assert spendable.tx_hash == b"\x00" assert spendable.tx_out_index == 0
def spendable_small_hex_large_vout(txout_small_coin_large_script): return Spendable.from_tx_out(txout_small_coin_large_script, b"\x00", 10)
def spendable_large_hex_large_vout(txout_large_coin_large_script): return Spendable.from_tx_out(txout_large_coin_large_script, b"\xFF", 10)