def into_transaction(self, dust_secret: CBitcoinSecret, dust_outpoint: OutPointWithTx, feerate): if dust_outpoint.prevout.scriptPubKey != pubkey_to_P2PKH_scriptPubkey( dust_secret.pub): print(b2x(dust_outpoint.prevout.scriptPubKey)) print(b2x(pubkey_to_P2PKH_scriptPubkey(dust_secret.pub))) raise Exception("Outpoint has incorrect scriptPubKey") sum_in = dust_outpoint.prevout.nValue fees = int(tx_size(1, 2) / 1000 * feerate) refund = sum_in - fees - self._life_signal_amount print('fee: %f' % fees) print('amount: %f' % (sum_in - fees)) redeemScript = self.redeemScript unsigned_tx = CTransaction([CTxIn(dust_outpoint.outpoint)], [ CTxOut(self._life_signal_amount, redeemScript.to_p2sh_scriptPubKey()), CTxOut(refund, pubkey_to_P2PKH_scriptPubkey(dust_secret.pub)) ]) # spend the dust input sighash = SignatureHash(dust_outpoint.prevout.scriptPubKey, unsigned_tx, 0, SIGHASH_ALL) sig = dust_secret.sign(sighash) + bytes([SIGHASH_ALL]) sigScript = CScript([sig, dust_secret.pub]) signed_input = [CTxIn(unsigned_tx.vin[0].prevout, sigScript)] return CTransaction(signed_input, unsigned_tx.vout, unsigned_tx.nLockTime)
def spend_p2sh_mediator(redeemScript, txins_str, amounts, daddrs, sig, rein): txin_redeemScript = CScript(x(redeemScript)) txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txins_obj = [] for txin_str in txins_str.split(): txin_list = txin_str.split("-") txins_obj.append( CMutableTxIn(COutPoint(lx(txin_list[0]), int(txin_list[1])))) txouts = [] len_amounts = len(amounts) for i in range(0, len_amounts): txouts.append( CMutableTxOut( round(amounts[i], 8) * COIN, CBitcoinAddress(daddrs[i]).to_scriptPubKey())) tx = CMutableTransaction(txins_obj, txouts) seckey = CBitcoinSecret(rein.user.dkey) ntxins = len(txins_obj) sig_list = [] for s in sig.split(): sig_list.append(x(s)) sig2_str = "" for i in range(0, ntxins): sighash = SignatureHash(txin_redeemScript, tx, i, SIGHASH_ALL) sig2 = seckey.sign(sighash) + x("01") sig2_str += " " + b2x(sig2) txins_obj[i].scriptSig = CScript( [OP_0, sig2, sig_list[i], txin_redeemScript]) VerifyScript(txins_obj[i].scriptSig, txin_scriptPubKey, tx, i, (SCRIPT_VERIFY_P2SH, )) tx_bytes = tx.serialize() hash = sha256(sha256(tx_bytes).digest()).digest() txid = b2x(hash[::-1]) txid_causeway = broadcast_tx(b2x(tx_bytes), rein) return (txid, sig2_str[1:])
def partial_spend_p2sh_mediator(redeemScript, rein, mediator_address, mediator_sig=False): txin_redeemScript = CScript(x(redeemScript)) txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) (txins, total_value) = unspent_txins(txin_p2sh_address, rein.testnet) if len(txins) == 0: raise ValueError('No unspent txins found') txins_str = "" txins_obj = [] for txid, vout in txins: txins_str += " " + txid + "-" + str(vout) txins_obj.append(CMutableTxIn(COutPoint(lx(txid), vout))) fee = 0.00025 amount = round(total_value - fee, 8) if amount <= 0: raise ValueError('Not enough value in the inputs') if mediator_sig: txout = CMutableTxOut( amount * COIN, CBitcoinAddress(mediator_address).to_scriptPubKey()) tx = CMutableTransaction(txins_obj, [txout]) seckey = CBitcoinSecret(rein.user.dkey) ntxins = len(txins_obj) sig = "" for i in range(0, ntxins): sighash = SignatureHash(txin_redeemScript, tx, i, SIGHASH_ALL) sig += " " + b2x(seckey.sign(sighash) + x("01")) return (txins_str[1:], "{:.8f}".format(amount), str(mediator_address), sig[1:]) return (txins_str[1:], "{:.8f}".format(amount), str(mediator_address))
def _scriptSig_by_sgx(self, seckey_sgx: CBitcoinSecret, unsigned_tx, n_in): # sgx spends the true branch branch = OP_TRUE sighash = SignatureHash(self._redeemScript(), unsigned_tx, n_in, SIGHASH_ALL) sig = seckey_sgx.sign(sighash) + bytes([SIGHASH_ALL]) return CScript([sig, seckey_sgx.pub, branch, self._redeemScript()])
def scriptSig_by_key1(self, secret_key: CBitcoinSecret, unsigned_tx: CTransaction, which_to_sign): assert secret_key.pub == self._key1 branch = OP_TRUE sighash = SignatureHash(self.redeemScript, unsigned_tx, which_to_sign, SIGHASH_ALL) sig = secret_key.sign(sighash) + bytes([SIGHASH_ALL]) return CScript([sig, self._key1, branch, self.redeemScript])
def partial_spend_p2sh(redeemScript, rein, daddr=None, alt_amount=None, alt_daddr=None): if daddr is None: daddr = rein.user.daddr txin_redeemScript = CScript(x(redeemScript)) txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) (txins, total_value) = unspent_txins(txin_p2sh_address, rein.testnet) if len(txins) == 0: raise ValueError( 'Primary escrow is empty. Please inform client to add funds.') txins_str = "" txins_obj = [] for txid, vout in txins: txins_str += " " + txid + "-" + str(vout) txins_obj.append(CMutableTxIn(COutPoint(lx(txid), vout))) fee = float(PersistConfig.get(rein, 'fee', 0.001)) amount = round(total_value - fee, 8) if alt_amount: amount = round(amount - alt_amount, 8) if amount <= 0. or alt_amount > total_value - fee: click.echo("amount: " + str(amount) + " alt_amount: " + str(alt_amount) + " total_value: " + str(total_value)) raise ValueError( 'Primary escrow balance too low. Please inform client to add funds.' ) txouts = [] txout = CMutableTxOut(amount * COIN, CBitcoinAddress(daddr).to_scriptPubKey()) txouts.append(txout) if alt_amount: txout_alt = CMutableTxOut( round(alt_amount, 8) * COIN, CBitcoinAddress(alt_daddr).to_scriptPubKey()) txouts.append(txout_alt) tx = CMutableTransaction(txins_obj, txouts) ntxins = len(txins_obj) seckey = CBitcoinSecret(rein.user.dkey) sig = "" for i in range(0, ntxins): sighash = SignatureHash(txin_redeemScript, tx, i, SIGHASH_ALL) sig += " " + b2x(seckey.sign(sighash)) + "01" if alt_amount: return (txins_str[1:], "{:.8f}".format(amount), daddr, "{:.8f}".format(alt_amount), alt_daddr, sig[1:]) return (txins_str[1:], "{:.8f}".format(amount), daddr, sig[1:])
def partial_spend_p2sh_mediator_2 (redeemScript,txins_str,amount,daddr,rein): txin_redeemScript = CScript(x(redeemScript)) txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txins_obj = [] for txin_str in txins_str.split(): txin_list = txin_str.split("-") txins_obj.append(CMutableTxIn(COutPoint(lx(txin_list[0]),int(txin_list[1])))) txout = CMutableTxOut(amount*COIN,CBitcoinAddress(daddr).to_scriptPubKey()) tx = CMutableTransaction(txins_obj,[txout]) seckey = CBitcoinSecret(rein.user.dkey) ntxins = len(txins_obj) for i in range(0,ntxins): sighash = SignatureHash(txin_redeemScript,tx,i,SIGHASH_ALL) sig = seckey.sign(sighash)+x("01") txins_obj[i].scriptSig = CScript([OP_0, sig, txin_redeemScript]) #VerifyScript(txins_obj[i].scriptSig, txin_scriptPubKey, tx, i, (SCRIPT_VERIFY_P2SH,)) tx_bytes = tx.serialize() return b2x(tx_bytes)
def transfer(self, src_addrs_key_map: OrderedDict, dst_addrs_amount_map: dict, txfee: Decimal, auto_calc_pay_back: bool, pay_back_index: int = 0xfffffff, ensure_one_txout: bool = False) -> str: """ :param src_addrs_key_map: {'addr1': 'privkey1', 'addr2': 'privkey2' } 私钥为 hex字符串 :param dst_addrs_amount_map: {'addr1':Decimal(0.1234), 'addr2':Deciaml(0.234) } :param txfee: 矿工费 :param auto_calc_pay_back: 是否自动计算并找零 :param pay_back_index: 找零地址索引 即在 src_addrs_key_map 中的索引 :param ensure_one_txout: 确认只有一个交易输出(比如: 提币不需要找零的情况, 归集) :return: txid """ #仅支持 P2PKH 类型的from地址 assert isinstance(src_addrs_key_map, OrderedDict), 'src_addrs_key_map is not OrderedDict' assert isinstance(dst_addrs_amount_map, dict), 'dst_addrs_amount_map is not dict' assert Decimal('0.00001') <= txfee <= Decimal( '0.001'), 'invalid txfee, please check txfee' assert len( src_addrs_key_map) >= 1, 'src_addrs_key_map length must >= 1' assert not (True == auto_calc_pay_back == ensure_one_txout) , \ 'True == auto_calc_pay_back == ensure_one_txout , must be mutex ' if ensure_one_txout: assert (len(dst_addrs_amount_map) == 1 ), 'dst_addrs_amount_map length must equal 1' elif not auto_calc_pay_back: assert (len(dst_addrs_amount_map) >= 1), 'dst_addrs_amount_map length must >= 2' if auto_calc_pay_back and pay_back_index >= len(src_addrs_key_map): raise Exception('pay_back_index is to large') self.logger.info( f'dst_addrs_amount_map is { json.dumps(dst_addrs_amount_map, indent=4, default=decimal_default) }' ) assert self.ping() == True, 'bitcoind rpc is gone' total_amount = sum(dst_addrs_amount_map.values()) + txfee self.logger.info(f'total_amount is {total_amount}') source_addrs = list(src_addrs_key_map.keys()) #WARNING: 禁止打印私钥!!! is_enough, founded_utxos = self.search_utxo(addrs=source_addrs, total_amount=total_amount) if not is_enough: msg = 'balance is not enough' self.logger.error(msg) raise Exception(msg) self.logger.info( f'founded_utxos is { json.dumps(founded_utxos, indent=4, default=decimal_default) }' ) #设置全局变量 SelectParams(self.net_type) #构造inputs txins = [] utxo_owner_map = dict() for addr, utxos in founded_utxos.items(): assert addr in src_addrs_key_map, 'addr is not in src_addrs_key_map' for utxo in utxos: txin = CMutableTxIn( prevout=COutPoint(hash=lx(utxo['txid']), n=utxo['vout'])) txins.append(txin) #因为顺序不会被打乱, 所以可以直接使用 索引进行对应 utxo_owner_map[len(txins) - 1] = addr #构造outputs txouts = [] for to_addr, amount in dst_addrs_amount_map.items(): out = CMutableTxOut( nValue=amount * COIN, scriptPubKey=CBitcoinAddress(to_addr).to_scriptPubKey()) txouts.append(out) #自动结算 if auto_calc_pay_back: sum_in_satoshi = 0 for addr, utxos in founded_utxos.items(): for utxo in utxos: sum_in_satoshi += utxo['value'] pay_back_in_satoshi = int(sum_in_satoshi - int(total_amount * COIN)) #判断找零的金额, 如果太小就不要了, 作为矿工费 if pay_back_in_satoshi >= MIN_AVAILABLE_UTXO_VALUE_IN_SATOSHI: pay_back_addr = list(src_addrs_key_map.keys())[pay_back_index] pay_back_out = CMutableTxOut( nValue=pay_back_in_satoshi, scriptPubKey=CBitcoinAddress( pay_back_addr).to_scriptPubKey()) txouts.append(pay_back_out) muttx = CMutableTransaction(vin=txins, vout=txouts) #对每个 input 进行签名 for n in range(len(txins)): #查找这个utxo属于哪个地址 owner_addr = utxo_owner_map[n] privkey = src_addrs_key_map[owner_addr] if len(privkey) == 64: # hex wif_key = PrivKeyToWIFCompress(privkey, self.net_type != 'mainnet') seckey = CBitcoinSecret(s=wif_key) elif len(privkey) == 52: #base58格式 seckey = CBitcoinSecret(s=privkey) else: raise Exception("invalid privkey") txin_script_pubkey = CScript([ OP_DUP, OP_HASH160, Hash160(seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG ]) sig_hash = SignatureHash(txin_script_pubkey, muttx, n, SIGHASH_ALL) sig = seckey.sign(sig_hash) + bytes([SIGHASH_ALL]) muttx.vin[n].scriptSig = CScript([sig, seckey.pub]) #TODO: 处理验签失败抛异常 VerifyScript(muttx.vin[n].scriptSig, txin_script_pubkey, muttx, n, (SCRIPT_VERIFY_P2SH, )) pass raw_tx_hex = b2x(muttx.serialize()) self.logger.info(f'raw_tx_hex is: {raw_tx_hex}') # local_tx_hash = muttx.GetTxid() # self.logger.info(f'local_tx_hash is: {b2x(local_tx_hash)}') #广播交易 assert self.ping( ) == True, 'bitcoind rpc is gone' #测试 bitcoind的 rpc服务是否还在 txid = self.send_raw_tx(raw_tx_hex=raw_tx_hex) self.logger.info(f'send_raw_tx txid is: {txid}') return txid
txins, [txout] ) # maybe need to add parameter 'nLockTime=200'? It seems to work without this parameter, but slide 74 says it's needed #print("raw unsigned transaction: " + b2x(tx.serialize())) for index, txin in enumerate(txins): # Calculate the signature hash for that transaction. Note how the script we use # is the redeemScript, not the scriptPubKey. That's because when the CHECKSIG # operation happens EvalScript() will be evaluating the redeemScript, so the # corresponding SignatureHash() function will use that same script when it # replaces the scriptSig in the transaction being hashed with the script being # executed. sighash = SignatureHash(redeem_script, tx, index, SIGHASH_ALL) # Now sign it. We have to append the type of signature we want to the end, in # this case the usual SIGHASH_ALL. sig = secret.sign(sighash) + bytes([SIGHASH_ALL]) # Set the scriptSig of our transaction input appropriately. txin.scriptSig = CScript([sig, redeem_script]) # Verify the signature worked. This calls EvalScript() and actually executes # the opcodes in the scripts to see if everything worked out. If it doesn't an # exception will be raised. # this is commented out so that locktime requirement error is not hidden when this script is run before locktime requirement is satisfied # VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, index, (SCRIPT_VERIFY_P2SH,)) # Done setting up the transaction. Display it. # print('transaction: ' + b2x(tx.serialize())) # actually execute the transaction transactionid = proxy.sendrawtransaction(tx) # decode and print the resulting transaction id. Using bytes to little-endian hex function.
tx = CMutableTransaction([txin], [txout]) tx.nLockTime = nLockTime # Calculate the signature hash for that transaction. Note how the script we use # is the redeemScript, not the scriptPubKey. That's because when the CHECKSIG # operation happens EvalScript() will be evaluating the redeemScript, so the # corresponding SignatureHash() function will use that same script when it # replaces the scriptSig in the transaction being hashed with the script being # executed. sighash = SignatureHash(txin_redeemScript, tx, 0, SIGHASH_ALL) print("hash:", b2x(sighash)) print(b2x(bytes([SIGHASH_ALL]))) # Now sign it. We have to append the type of signature we want to the end, in # this case the usual SIGHASH_ALL. sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) print("sig:", b2x(sig)) # Set the scriptSig of our transaction input appropriately. txin.scriptSig = CScript([sig, txin_redeemScript]) # Verify the signature worked. This calls EvalScript() and actually executes # the opcodes in the scripts to see if everything worked out. If it doesn't an # exception will be raised. VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH, )) # Done! Print the transaction to standard output with the bytes-to-hex # function. txn = tx.serialize() print("raw size:", len(txn)) print("txid:", b2lx(tx.GetTxid()))
def sign_bounce_tx(self, sighash_hex): sighash = binascii.unhexlify(sighash_hex) seckey = CBitcoinSecret(self.key_pairs[self.my]["priv"]["wif"]) sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) return sig
def build_claim_tx(self, txid_hex): if self.secret_info["plaintext"] == None: raise Exception("We don't know the secret yet.") #Create redeem script. redeem_script = self.fund_redeem_script(self.their) #Convert to p2sh address. their_fund_address = self.script_to_address(redeem_script, self.their) #Check there is enough in the p2sh address. their_fund_tx = self.jsonrpc[self.their].gettransaction( txid_hex)["details"] found = 0 for tx_input in their_fund_tx: #Check it's the right input. if tx_input["address"] == their_fund_address: found = 1 if tx_input["amount"] + self.recv_amount > decimal.Decimal( coinbend.config["mining_fee"]["standard"]): raise Exception( "Their contract has not been sufficently funded.") break else: continue #Their side of the contract hasn't been funded. if not found: raise Exception("Their contract fund output was not detected.") #Generate address to receive redeemed output. if "receive" not in self.key_pairs: self.key_pairs["receive"] = self.key_pair_from_address( self.jsonrpc[self.their].getnewaddress(), self.their) #Load private key for signing. seckey = CBitcoinSecret(self.key_pairs[self.my]["priv"]["wif"]) #Generate p2sh script pub key. redeem_script_hash160 = self.hash160_script(redeem_script) txin_script_pub_key = CScript( [OP_HASH160, redeem_script_hash160["bin"], OP_EQUAL]) #Setup tx inputs and outputs. txid = lx(txid_hex) vout = 0 txin = CTxIn(COutPoint(txid, vout)) txout = CTxOut( (self.recv_amount - decimal.Decimal(coinbend.config["mining_fee"]["standard"])) * COIN, CBitcoinAddress( self.key_pairs["receive"]["addr"]["base58"]).to_scriptPubKey()) #Create unsigned transaction. tx = CTransaction([txin], [txout]) #Sign transactions. sighash = SignatureHash(redeem_script["bin"], tx, 0, SIGHASH_ALL) sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) txin.scriptSig = CScript([ bytes(self.secret_info["plaintext"].encode("ascii")), sig, OP_3, redeem_script["bin"] ]) #Return signed transaction hex. return b2x(tx.serialize())
# to a newly created address. dest_addr = bitcoin_rpc.getnewaddress() coinbase_amount = coinbase["vout"][0]["value"] fee = 10000 # in satoshi out_amount = coinbase_amount * COIN + burn_amount * COIN - fee txout = CMutableTxOut(out_amount, CBitcoinAddress(dest_addr).to_scriptPubKey()) # Create the transaction tx = CMutableTransaction([txin, txin2], [txout]) # Sign inputs. Start with the coinbase input. coinbase_address = coinbase["vout"][0]["scriptPubKey"]["addresses"][0] seckey_coinbase = CBitcoinSecret(bitcoin_rpc.dumpprivkey(coinbase_address)) sighash = SignatureHash(CScript(x(coinbase["vout"][0]["scriptPubKey"]["hex"])), tx, 0, SIGHASH_ALL) sig = seckey_coinbase.sign(sighash) + bytes([SIGHASH_ALL]) txin.scriptSig = CScript([sig]) # Sign "burn" address input. sighash = SignatureHash(CScript(multisig_script), tx, 1, SIGHASH_ALL) sig = seckey_multisig.sign(sighash) + bytes([SIGHASH_ALL]) txin2.scriptSig = CScript([OP_0, sig, redeemScript]) scriptPubKey = redeemScript.to_p2sh_scriptPubKey() # Submit transaction to bitcoind, create a block and check received amount. txid = bitcoin_rpc.sendrawtransaction(b2x(tx.serialize())) bitcoin_rpc.generate(1) print("Sent coins from \"burn\" address to own address (txid: %s)" % txid) assert (int(bitcoin_rpc.getreceivedbyaddress(str(dest_addr)) * COIN) == out_amount) print("Received %.8f (coinbase) + %.8f coins (\"burned\") - %.8f (fee)" %
def pay(self, pay, change_address=None, allow_zero_conf=False, randomize_change_idx=True, fee_strategy='optimal'): send = {} if isinstance(pay, list): for address, value in pay: send[address] = value else: send = pay coin_selection = self.client.coin_selection( self.identifier, send, lockUTXO=True, allow_zero_conf=allow_zero_conf, fee_strategy=fee_strategy) utxos = coin_selection['utxos'] fee = coin_selection['fee'] change = coin_selection['change'] if change > 0: if change_address is None: _, change_address = self.get_new_address_pair() send[change_address] = change txins = [] for utxo in utxos: txins.append(CMutableTxIn(COutPoint(lx(utxo['hash']), utxo['idx']))) txouts = [] change_txout = None for address, value in send.items(): txout = CMutableTxOut(value, CBitcoinAddress(address).to_scriptPubKey()) if address == change_address: change_txout = txout txouts.append(txout) # randomly move the change_txout if randomize_change_idx and change_txout: txouts.remove(change_txout) txouts.insert(random.randrange(len(txouts) + 1), change_txout) tx = CMutableTransaction(txins, txouts) for idx, utxo in enumerate(utxos): path = utxo['path'].replace("M/", "") key = self.primary_private_key.subkey_for_path(path) redeemScript = CScript(x(utxo['redeem_script'])) sighash = SignatureHash(redeemScript, tx, idx, SIGHASH_ALL) ckey = CBitcoinSecret(key.wif()) sig = ckey.sign(sighash) + struct.pack("B", SIGHASH_ALL) txins[idx].scriptSig = CScript([OP_0, sig, redeemScript]) signed = self.client.send_transaction(self.identifier, b2x(tx.serialize()), [utxo['path'] for utxo in utxos], check_fee=True) return signed['txid']