def test_make_raw_transaction(): tx = make_raw_transaction("bitcoin", [], [], Locktime(0)) assert isinstance(tx, MutableTransaction) tx = make_raw_transaction("peercoin", [], [], Locktime(300000)) assert isinstance(tx, MutableTransaction)
def _get_unsigned_txn(self): # outputs_amounts is copied so any instance can be modified with change_fee, # and will still function correctly, i.e the change address won't already # be in the self._outputs_amounts dict self._modified_outputs_amounts = self.outputs_amounts.copy() # adding change address to outputs, if there is leftover balance that isn't dust if self._change_amount > 0: self._modified_outputs_amounts[self.change_address] = self._change_amount outputs = [] for i, (addr, amount) in enumerate(self._modified_outputs_amounts.items()): outputs.append(TxOut( value=amount, n=i, script_pubkey=self.get_script_pubkey(addr) )) inputs = [] for t in self._specific_utxo_data: # build inputs using the UTXO data in self._specific_utxo_data, # script_sig is empty as the transaction will be signed later inputs.append( TxIn(txid=t[0], txout=t[1], script_sig=ScriptSig.empty(), sequence=Sequence.max(), witness=Witness([StackData.zero()])) if self.is_segwit else None, ) if self.is_segwit: transaction = MutableSegWitTransaction( version=TX_VERSION, ins=inputs, outs=outputs, locktime=Locktime(self.locktime), ) else: transaction = MutableTransaction( version=TX_VERSION, ins=inputs, outs=outputs, locktime=Locktime(self.locktime) ) return transaction
def test_sign_transaction(): network_params = net_query('tppc') provider = Explorer(network='tppc') key = Kutil(network='tppc', privkey=bytearray.fromhex('9e321f5379c2d1c4327c12227e1226a7c2e08342d88431dcbb0063e1e715a36c') ) dest_address = 'mwn75Gavp6Y1tJxca53HeCj5zzERqWagr6' unspent = provider.select_inputs(key.address, 0.63) # 0.69 output = tx_output(network='tppc', value=Decimal(0.1), n=0, script=p2pkh_script(network='tppc', address=dest_address) ) unsigned = MutableTransaction( version=1, ins=unspent['utxos'], outs=[output], locktime=Locktime(0), network=network_params, timestamp=int(time.time()), ) parent_outputs = [find_parent_outputs(provider, i) for i in unsigned.ins] solver = P2pkhSolver(key._private_key) signed = unsigned.spend(parent_outputs, [solver for i in parent_outputs]) assert isinstance(signed, Transaction)
def test_sign_transaction(): network_params = net_query('tppc') provider = Cryptoid(network='tppc') key = pa.Kutil( network='tppc', privkey=bytearray.fromhex( '9e321f5379c2d1c4327c12227e1226a7c2e08342d88431dcbb0063e1e715a36c') ) dest_address = pa.Kutil(network='tppc').address unspent = provider.select_inputs(key.address, 1) output = tx_output(network='tppc', value=Decimal(0.1), n=0, script=p2pkh_script(network='tppc', address=dest_address)) unsigned = MutableTransaction( version=1, ins=unspent['utxos'], outs=[output], locktime=Locktime(0), network=network_params, timestamp=int(time.time()), ) assert isinstance(sign_transaction(provider, unsigned, key), Transaction)
def build_transaction(self, wallet, htlc, amount, locktime=0): """ Build bitcoin fund transaction. :param wallet: bitcoin sender wallet. :type wallet: bitcoin.wallet.Wallet :param htlc: bitcoin hash time lock contract (HTLC). :type htlc: bitcoin.htlc.HTLC :param amount: bitcoin amount to fund. :type amount: int :param locktime: bitcoin transaction lock time, defaults to 0. :type locktime: int :returns: FundTransaction -- bitcoin fund transaction instance. >>> from shuttle.providers.bitcoin.transaction import FundTransaction >>> fund_transaction = FundTransaction(network="testnet") >>> fund_transaction.build_transaction(sender_wallet, htlc, 10000) <shuttle.providers.bitcoin.transaction.FundTransaction object at 0x0409DAF0> """ # Checking build transaction arguments instance if not isinstance(wallet, Wallet): raise TypeError( "invalid wallet instance, only takes bitcoin Wallet class") if not isinstance(htlc, HTLC): raise TypeError( "invalid htlc instance, only takes bitcoin HTLC class") if not isinstance(amount, int): raise TypeError("invalid amount instance, only takes integer type") # Setting wallet, htlc, amount and unspent self.wallet, self.htlc, self.amount = wallet, htlc, amount # Getting unspent transaction output self.unspent = self.wallet.unspent() # Setting previous transaction indexes self.previous_transaction_indexes = \ self.get_previous_transaction_indexes(amount=self.amount) # Getting transaction inputs and amount inputs, amount = self.inputs(self.unspent, self.previous_transaction_indexes) # Calculating bitcoin fee self.fee = fee_calculator(len(inputs), 2) if amount < (self.amount + self.fee): raise BalanceError("insufficient spend utxos") # Building mutable bitcoin transaction self.transaction = MutableTransaction( version=self.version, ins=inputs, outs=[ # Funding into hash time lock contract script hash TxOut(value=self.amount, n=0, script_pubkey=P2shScript.unhexlify(self.htlc.hash())), # Controlling amounts when we are funding on htlc script. TxOut(value=amount - (self.fee + self.amount), n=1, script_pubkey=P2pkhScript.unhexlify(self.wallet.p2pkh())) ], locktime=Locktime(locktime)) return self
def make_raw_transaction(inputs: list, outputs: list, locktime=Locktime(0), timestamp: int = int(time()), version=1): '''create raw transaction''' return MutableTransaction(version, timestamp, inputs, outputs, locktime)
def min_locktime(locktimes): m = 0 res = Locktime(0) for lock in locktimes: if m is 0 or lock.n < m: m = lock.n res = lock return res
def from_json(cls, tx_json, network=PeercoinMainnet): return cls( version=tx_json['version'], ins=[TxIn.from_json(txin_json) for txin_json in tx_json['vin']], outs=[TxOut.from_json(txout_json) for txout_json in tx_json['vout']], locktime=Locktime(tx_json['locktime']), txid=tx_json['txid'], network=network, timestamp=tx_json['time'], )
def createContractTx(SigScript, PubScript): # Array of outputs . e.g see outs comment # FundingWitness=SegWit_Witness(funding_sig(),funding_pubkey()); # SigScript1 = Script.unhexlify('48304502210083e6e7507e838a190f0443441c0b62d2df94673887f4482e27e89ff415a90392022050575339c649b85c04bb410a00b62325c1b82c537135fa62fb34fae2c9a30b0b01210384478d41e71dc6c3f9edde0f928a47d1b724c05984ebfb4e7d0422e80abe95ff') ContractTx = MutableSegWitTransaction( version=1, ins=SigScript, #,witness=FundingWitness)], outs= PubScript, #[TxOut(value=1,n=0,script_pubkey=FundingPubkey)], locktime=Locktime(0)) locktime=Locktime(0)) #TODO Failing here .why do we need this ? return ContractTx
def open_tx(committer, secret, commit_tx_hash): # 创建输入脚本 p2pkh_solver = P2pkhSolver(committer.privk) hasklock_solver = HashlockSolver(secret.encode(), p2pkh_solver) if_solver = IfElseSolver( Branch.IF, # branch selection hasklock_solver) # 创建输出脚本 script = P2pkhScript(committer.pubk) # 获取commit交易 to_spend_raw = get_raw_tx(commit_tx_hash, coin_symbol) to_spend = TransactionFactory.unhexlify(to_spend_raw) # 获取罚金数额 penalty = int(float(to_spend.to_json()['vout'][0]['value']) * (10**8)) # 估算挖矿费用 print('estimating mining fee...') mining_fee_per_kb = get_mining_fee_per_kb(coin_symbol, committer.api_key, condidence='high') estimated_tx_size = cal_tx_size_in_byte(inputs_num=1, outputs_num=1) mining_fee = int(mining_fee_per_kb * (estimated_tx_size / 1000)) * 2 # 创建交易 unsigned = MutableTransaction(version=2, ins=[ TxIn(txid=to_spend.txid, txout=0, script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=[ TxOut(value=penalty - mining_fee, n=0, script_pubkey=script), ], locktime=Locktime(0)) # 修改交易 signed = unsigned.spend([to_spend.outs[0]], [if_solver]) # 广播交易 print('open_tx_hex: ', signed.hexlify()) msg = pushtx(coin_symbol=coin_symbol, api_key=committer.api_key, tx_hex=signed.hexlify()) format_output(msg) return msg['tx']['hash']
def make_raw_transaction( inputs: list, outputs: list, locktime: Locktime = Locktime(0), timestamp: int = int(time()), version: int = 1, ) -> Transaction: '''create raw transaction''' return Transaction(version=version, timestamp=timestamp, ins=inputs, outs=outputs, locktime=locktime)
def solve(self, network: str = config["network"]) -> IfElseSolver: if self._path is None: self._path = config["bip44_path"].format( account=self._account, change=(1 if self._change else 0), address=self._address) return IfElseSolver( branch=Branch.ELSE, inner_solver=AbsoluteTimelockSolver( locktime=Locktime(n=self._endtime), inner_solver=P2pkhSolver(privk=PrivateKey.unhexlify( hexa=Wallet(network=network).from_root_xprivate_key( xprivate_key=self._xprivate_key, strict=self._strict). from_path(path=self._path).private_key()))))
def spendCb(fee, reward, objTx, outputs, cbSolver, dictIdToPub): """ create a single coinbase spend :param fee: fee :param reward: block reward in sat :param objTx: coinbase tx :param outputs: lst of channels (copies from originals) :param cbSolver: solver :param bPubMiner: pub of the miner :return: tx that spends coinbase """ outs = [] totVal = 0 chanIds = [] for o in outputs: v = int(o.value) bPubN1 = dictIdToPub[str(o.node1.nodeid)] bPubN2 = dictIdToPub[str(o.node2.nodeid)] if bPubN1.compressed < bPubN2.compressed: # lexicographical ordering multisig_script = MultisigScript(2, bPubN1, bPubN2, 2) else: multisig_script = MultisigScript(2, bPubN2, bPubN1, 2) p2wsh_multisig = P2wshV0Script(multisig_script) totVal += v outs += [TxOut(value=v, n=0, script_pubkey=p2wsh_multisig)] chanIds += [o.channelid] change = reward - totVal - fee outsWithChange = outs + [ TxOut(value=change, n=0, script_pubkey=objTx.outs[0].script_pubkey) ] unsignedCb = MutableTransaction(version=1, ins=[ TxIn(txid=objTx.txid, txout=0, script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=outsWithChange, locktime=Locktime(0)) cbTx = unsignedCb.spend([objTx.outs[0]], [cbSolver]) return cbTx, (cbTx.txid, chanIds)
def fundingTxn(): Signer = ScriptSig.empty() outvalue = fundingInput_value(fundingTxIn, 0) - 400 MultiSigTx = MutableSegWitTransaction( version=1, #Publish to the Blockchain ins=[ TxIn(txid=fundingTxIn_id, txout=0, script_sig=Signer, sequence=Sequence.max()) ], outs=[TxOut(value=outvalue, n=0, script_pubkey=FundingScript) ], # todo must change this to access the contract script locktime=Locktime(0)) MultiSigTxSigned = MultiSigTx.spend( [fundingTxIn.outs[0]], [funding_sig]) # ToDo Failing here - spend attribute not found print("funding tx signed ", MultiSigTxSigned.hexlify()) # return MultiSigTx,p2sh_solver,MultiSigTxSigned.outs[0]; # TODo when to Return Signed MultiSigTransaction return MultiSigTxSigned.txid, MultiSigTxSigned.outs[0]
def sweepTx(MultiSigTx, MultiSigTxOutput, MultiSigTxSolver, to_pubkey, to_index, to_value): to_spend = MultiSigTx unsigned = MutableSegWitTransaction( version=1, ins=[ TxIn( txid=to_spend, #.txid, txout=to_index, script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=[ TxOut(value=to_value - txFee, n=0, script_pubkey=P2pkhScript(to_pubkey)) ], # todo make funding_pubkey a parameter. This must sweep back tp A & B locktime=Locktime(0)) solver = MultiSigTxSolver signed = unsigned.spend( [MultiSigTxOutput], [solver]) #print ("Return tx signed ",signed.hexlify()) return signed.hexlify()
def build_transaction( self, address: str, recipients: dict, unit: str = config["unit"], locktime: int = config["locktime"]) -> "NormalTransaction": """ Build Bitcoin normal transaction. :param address: Bitcoin sender address. :type address: str :param recipients: Recipients Bitcoin address and amount. :type recipients: dict :param unit: Bitcoin unit, default to ``Satoshi``. :type unit: str :param locktime: Bitcoin transaction lock time, defaults to ``0``. :type locktime: int :returns: NormalTransaction -- Bitcoin normal transaction instance. >>> from swap.providers.bitcoin.transaction import NormalTransaction >>> normal_transaction: NormalTransaction = NormalTransaction(network="testnet") >>> normal_transaction.build_transaction(address="mkFWGt4hT11XS8dJKzzRFsTrqjjAwZfQAC", recipients={"2N6kHwQy6Ph5EdKNgzGrcW2WhGHKGfmP5ae": 10000000}, locktime=0) <swap.providers.bitcoin.transaction.NormalTransaction object at 0x0409DAF0> """ # Check parameter instances if not is_address(address, self._network): raise AddressError( f"Invalid Bitcoin sender '{address}' {self._network} address.") if unit not in ["BTC", "mBTC", "Satoshi"]: raise UnitError( "Invalid Bitcoin unit, choose only 'BTC', 'mBTC' or 'Satoshi' units." ) self._address, outputs, self._amount = (address, [], ( sum(recipients.values()) if unit == "Satoshi" else amount_unit_converter( amount=sum(recipients.values()), unit_from=f"{unit}2Satoshi"))) # Get Sender UTXO's self._utxos = get_utxos(address=self._address, network=self._network) # Outputs action for _address, _amount in recipients.items(): if not is_address(_address, self._network): raise AddressError( f"Invalid Bitcoin recipients '{_address}' {self._network} address." ) outputs.append( TxOut(value=int(_amount), n=len(outputs), script_pubkey=get_address_hash(address=_address, script=True))) # Get previous transaction indexes self._previous_transaction_indexes, max_amount = _get_previous_transaction_indexes( utxos=self._utxos, amount=self._amount, transaction_output=len(outputs)) # Build transaction inputs inputs, amount = _build_inputs( utxos=self._utxos, previous_transaction_indexes=self._previous_transaction_indexes) # Calculate the fee self._fee = fee_calculator(len(inputs), len(outputs)) if amount < self._amount: raise BalanceError("Insufficient spend UTXO's", "you don't have enough amount.") elif amount < (self._amount + self._fee): raise BalanceError( f"You don't have enough amount to pay '{self._fee}' Satoshi fee", f"you can spend maximum '{amount - self._fee}' Satoshi amount." ) return_amount: int = int(amount - (self._amount + self._fee)) if return_amount != 0: outputs.append( TxOut(value=return_amount, n=len(outputs), script_pubkey=get_address_hash(address=self._address, script=True))) # Build mutable transaction self._transaction = MutableTransaction(version=self._version, ins=inputs, outs=outputs, locktime=Locktime(locktime)) # Set transaction type self._type = "bitcoin_normal_unsigned" return self
def test_all(self): global keys priv = ExtendedPrivateKey.decode(keys[0][1]).key pk = priv.pub() addr_string = str(pk.to_address()) utxo = [] for i in range(3): # create 3 tx to add to UTXO txid = regtest.send_rpc_cmd(['sendtoaddress', addr_string, '100'], 0) to_spend = Transaction.unhexlify( regtest.send_rpc_cmd(['getrawtransaction', txid, '0'], 0)) txout = None for out in to_spend.outs: if str(out.script_pubkey.address()) == addr_string: txout = out break assert txout is not None utxo.append({ 'txid': txid, 'txout': txout, 'solver': P2pkhSolver(priv), 'next_seq': Sequence.max(), 'next_locktime': Locktime(0) }) regtest.send_rpc_cmd(['generate', '100'], 0) generate = False next_locktime = Locktime(0) next_sequence = Sequence.max() i = 0 while i < len(self.all) - 2: print('{:04d}\r'.format(i), end='', flush=True) ins = [ MutableTxIn(unspent['txid'], unspent['txout'].n, ScriptSig.empty(), unspent['next_seq']) for unspent in utxo ] outs = [] prev_types = [] for j, (unspent, script) in enumerate(zip(utxo, self.all[i:i + 3])): outs.append( TxOut(unspent['txout'].value - 1000000, j, script[0])) prev_types.append(script[2]) tx = MutableTransaction( 2, ins, outs, min_locktime(unspent['next_locktime'] for unspent in utxo)) mutable = copy.deepcopy(tx) tx = tx.spend([unspent['txout'] for unspent in utxo], [unspent['solver'] for unspent in utxo]) # print('====================') # print('txid: {}'.format(tx.txid)) # print() # print(tx) # print() # print('raw: {}'.format(tx.hexlify())) # print('prev_scripts, amounts, solvers:') print('TX: {}'.format(i)) regtest.send_rpc_cmd(['sendrawtransaction', tx.hexlify()], 0) print('Mempool size: {}'.format( len(regtest.send_rpc_cmd(['getrawmempool'], 0)))) if cmdline_args.dumpfile is not None: with open(cmdline_args.dumpfile, 'a') as out: for j, unspent in enumerate(utxo): json.dump( self.json_dump(unspent, tx.ins[j], j, copy.deepcopy(mutable).to_segwit()), out) out.write('\n') utxo = [] for j, (output, prev_type) in enumerate(zip(tx.outs, prev_types)): if 'time' in prev_type: if 'absolute' in prev_type: next_locktime = Locktime(100) next_sequence = Sequence(0xfffffffe) if 'relative' in prev_type: next_sequence = Sequence(3) generate = True else: next_locktime = Locktime(0) next_sequence = Sequence.max() utxo.append({ 'txid': tx.txid, 'txout': output, 'solver': self.all[i + j][1][0], # solver 'next_seq': next_sequence, 'next_locktime': next_locktime }) if generate: regtest.send_rpc_cmd(['generate', '4'], 0) generate = False if not i % 10: print('generating 2') regtest.send_rpc_cmd(['generate', '2'], 0) i += 1 ins = [ MutableTxIn(unspent['txid'], unspent['txout'].n, ScriptSig.empty(), unspent['next_seq']) for unspent in utxo ] tx = MutableTransaction( 2, ins, [ TxOut( sum(unspent['txout'].value for unspent in utxo) - 1000000, 0, self.final['script']) ], min_locktime(unspent['next_locktime'] for unspent in utxo)) tx = tx.spend([unspent['txout'] for unspent in utxo], [unspent['solver'] for unspent in utxo]) # print('====================') # print('txid: {}'.format(tx.txid)) # print() # print(tx) # print() # print('raw: {}'.format(tx.hexlify())) # print('prev_scripts, amounts, solvers:') # for unspent in utxo: # print(unspent['txout'].script_pubkey, unspent['txout'].value, unspent['solver'].__class__.__name__) regtest.send_rpc_cmd(['sendrawtransaction', tx.hexlify()], 0) regtest.teardown()
def get_args(): return Locktime(100),
def build_transaction(self, transaction_id, wallet, amount, secret=None, locktime=0): """ Build bitcoin refund transaction. :param transaction_id: bitcoin fund transaction id to redeem. :type transaction_id: str :param wallet: bitcoin sender wallet. :type wallet: bitcoin.wallet.Wallet :param amount: bitcoin amount to withdraw. :type amount: int :param secret: secret passphrase. :type secret: str :param locktime: bitcoin transaction lock time, defaults to 0. :type locktime: int :returns: RefundTransaction -- bitcoin refund transaction instance. >>> from shuttle.providers.bitcoin.transaction import RefundTransaction >>> refund_transaction = RefundTransaction(network="testnet") >>> refund_transaction.build_transaction(fund_transaction_id, sender_wallet, 10000) <shuttle.providers.bitcoin.transaction.RefundTransaction object at 0x0409DAF0> """ # Checking build transaction arguments instance if not isinstance(transaction_id, str): raise TypeError("invalid amount instance, only takes string type") if not isinstance(wallet, Wallet): raise TypeError( "invalid wallet instance, only takes bitcoin Wallet class") if secret is not None and not isinstance(secret, str): raise TypeError("invalid secret instance, only takes string type") # Setting transaction_id and wallet self.transaction_id, self.wallet, self.secret = transaction_id, wallet, secret # Getting transaction detail by id self.transaction_detail = get_transaction_detail(self.transaction_id) # Checking transaction outputs if "outputs" not in self.transaction_detail: raise NotFoundError("not found htlc in this %s hash" % self.transaction_id) # Hash time lock contract output self.htlc = self.transaction_detail["outputs"][0] # Sender account output sender_address = P2pkhScript.unhexlify( self.transaction_detail["outputs"][1]["script"]).address( mainnet=self.mainnet) self.sender_account = Wallet(network=self.network).from_address( str(sender_address)) # HTLC info's htlc_amount = self.htlc["value"] htlc_script = P2shScript.unhexlify(self.htlc["script"]) # Calculating fee self.fee = fee_calculator(1, 1) if amount < self.fee: raise BalanceError("insufficient spend utxos") elif not htlc_amount >= (amount - self.fee): raise BalanceError("insufficient spend utxos", "maximum withdraw %d" % htlc_amount) # Building mutable bitcoin transaction self.transaction = MutableTransaction( version=self.version, ins=[ TxIn(txid=self.transaction_id, txout=0, script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=[ TxOut(value=(amount - self.fee), n=0, script_pubkey=P2pkhScript.unhexlify( self.sender_account.p2pkh())) ], locktime=Locktime(locktime)) return self
to_spend = TransactionFactory.unhexlify(to_spend_raw) unsigned = MutableTransaction(version=2, ins=[ TxIn(txid=to_spend.txid, txout=0, script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=[ TxOut(value=penalty, n=0, script_pubkey=lock_time_script), TxOut(value=balance - penalty - mining_fee, n=1, script_pubkey=change_script) ], locktime=Locktime(0)) # 输入脚本 solver = P2pkhSolver(privk) # 修改交易 signed = unsigned.spend([to_spend.outs[0]], [solver]) print('commit_tx_hex: ', signed.hexlify()) # 发送交易 from blockcypher import pushtx tx = pushtx(coin_symbol=coin_symbol, api_key=api_key, tx_hex=signed.hexlify()) format_output(tx)
def test_all(self): global keys priv = PrivateKey.from_bip32(keys[0][1]) pk = priv.pub() addr_string = str(pk.to_address()) utxo = [] for i in range(3): # create 3 tx to add to UTXO txid = regtest.send_rpc_cmd(['sendtoaddress', addr_string, '100'], 0) to_spend = Transaction.unhexlify( regtest.send_rpc_cmd(['getrawtransaction', txid, '0'], 0)) txout = None for out in to_spend.outs: if str(out.script_pubkey.address()) == addr_string: txout = out break assert txout is not None utxo.append({ 'txid': txid, 'txout': txout, 'solver': P2pkhSolver(priv), 'next_seq': Sequence.max(), 'next_locktime': Locktime(0) }) regtest.send_rpc_cmd(['generate', '100'], 0) generate = False next_locktime = Locktime(0) next_sequence = Sequence.max() i = 0 # 1785 # 382 # 2376 # 1180 # while i < len(self.all) - 2: print('{:04d}\r'.format(i), end='') ins = [ MutableTxIn(unspent['txid'], unspent['txout'].n, ScriptSig.empty(), unspent['next_seq']) for unspent in utxo ] outs = [] prev_types = [] for j, (unspent, script) in enumerate(zip(utxo, self.all[i:i + 3])): outs.append( TxOut(unspent['txout'].value - 1000000, j, script[0])) prev_types.append(script[2]) tx = MutableTransaction( 2, ins, outs, min_locktime(unspent['next_locktime'] for unspent in utxo)) tx = tx.spend([unspent['txout'] for unspent in utxo], [unspent['solver'] for unspent in utxo]) # print('====================') # print('txid: {}'.format(tx.txid)) # print() # print(tx) # print() # print('raw: {}'.format(tx.hexlify())) # print('prev_scripts, amounts, solvers:') for unspent in utxo: if isinstance(unspent['solver'], P2shSolver): if isinstance(unspent['solver'].redeem_script_solver, P2wshV0Solver): prev = unspent[ 'solver'].redeem_script_solver.witness_script else: prev = unspent['solver'].redeem_script elif isinstance(unspent['solver'], P2wshV0Solver): prev = unspent['solver'].witness_script else: prev = unspent['txout'].script_pubkey print(prev, unspent['txout'].value, unspent['solver'].__class__.__name__) regtest.send_rpc_cmd(['sendrawtransaction', tx.hexlify()], 0) utxo = [] for j, (output, prev_type) in enumerate(zip(tx.outs, prev_types)): if 'time' in prev_type: if 'absolute' in prev_type: next_locktime = Locktime(100) next_sequence = Sequence(0xfffffffe) if 'relative' in prev_type: next_sequence = Sequence(3) generate = True else: next_locktime = Locktime(0) next_sequence = Sequence.max() utxo.append({ 'txid': tx.txid, 'txout': output, 'solver': self.all[i + j][1][0], # solver 'next_seq': next_sequence, 'next_locktime': next_locktime }) if generate: regtest.send_rpc_cmd(['generate', '4'], 0) generate = False if not i % 10: regtest.send_rpc_cmd(['generate', '2'], 0) i += 1 ins = [ MutableTxIn(unspent['txid'], unspent['txout'].n, ScriptSig.empty(), unspent['next_seq']) for unspent in utxo ] tx = MutableTransaction( 2, ins, [ TxOut( sum(unspent['txout'].value for unspent in utxo) - 1000000, 0, self.final['script']) ], min_locktime(unspent['next_locktime'] for unspent in utxo)) tx = tx.spend([unspent['txout'] for unspent in utxo], [unspent['solver'] for unspent in utxo]) # print('====================') # print('txid: {}'.format(tx.txid)) # print() # print(tx) # print() # print('raw: {}'.format(tx.hexlify())) # print('prev_scripts, amounts, solvers:') for unspent in utxo: print(unspent['txout'].script_pubkey, unspent['txout'].value, unspent['solver'].__class__.__name__) regtest.send_rpc_cmd(['sendrawtransaction', tx.hexlify()], 0) regtest.teardown()
def build_transaction(self, transaction_id, wallet, amount, locktime=0): """ Build Bitcoin refund transaction. :param transaction_id: Bitcoin fund transaction id to redeem. :type transaction_id: str :param wallet: Bitcoin sender wallet. :type wallet: bitcoin.wallet.Wallet :param amount: Bitcoin amount to withdraw. :type amount: int :param locktime: Bitcoin transaction lock time, defaults to 0. :type locktime: int :returns: RefundTransaction -- Bitcoin refund transaction instance. >>> from shuttle.providers.bitcoin.transaction import RefundTransaction >>> from shuttle.providers.bitcoin.wallet import Wallet >>> sender_wallet = Wallet(network="testnet").from_passphrase("meherett") >>> refund_transaction = RefundTransaction(network="testnet") >>> refund_transaction.build_transaction(transaction_id="1006a6f537fcc4888c65f6ff4f91818a1c6e19bdd3130f59391c00212c552fbd", wallet=sender_wallet, amount=10000) <shuttle.providers.bitcoin.transaction.RefundTransaction object at 0x0409DAF0> """ # Checking parameter instances if not isinstance(transaction_id, str): raise TypeError("invalid amount instance, only takes string type") if not isinstance(wallet, Wallet): raise TypeError( "invalid wallet instance, only takes Bitcoin Wallet class") # Setting transaction_id and wallet self.transaction_id, self.wallet = transaction_id, wallet # Getting transaction detail by id self.transaction_detail = get_transaction_detail(self.transaction_id) # Getting Hash time lock contract output detail self.htlc_detail = self.transaction_detail["outputs"][0] # Getting HTLC funded amount balance htlc_amount = self.htlc_detail["value"] # Calculating fee self._fee = fee_calculator(1, 1) if amount < self._fee: raise BalanceError("insufficient spend utxos") elif not htlc_amount >= (amount - self._fee): raise BalanceError("insufficient spend utxos", f"maximum you can withdraw {htlc_amount}") # Building mutable Bitcoin transaction self.transaction = MutableTransaction( version=self.version, ins=[ TxIn(txid=self.transaction_id, txout=0, script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=[ TxOut(value=(amount - self._fee), n=0, script_pubkey=P2pkhScript.unhexlify( hex_string=self.wallet.p2pkh())) ], locktime=Locktime(locktime)) self._type = "bitcoin_refund_unsigned" return self
def build_transaction(self, wallet, htlc, amount, locktime=0): """ Build Bitcoin fund transaction. :param wallet: Bitcoin sender wallet. :type wallet: bitcoin.wallet.Wallet :param htlc: Bitcoin hash time lock contract (HTLC). :type htlc: bitcoin.htlc.HTLC :param amount: Bitcoin amount to fund. :type amount: int :param locktime: Bitcoin transaction lock time, defaults to 0. :type locktime: int :returns: FundTransaction -- Bitcoin fund transaction instance. >>> from shuttle.providers.bitcoin.htlc import HTLC >>> from shuttle.providers.bitcoin.transaction import FundTransaction >>> from shuttle.providers.bitcoin.wallet import Wallet >>> htlc = HTLC(network="testnet").init("821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e0158", "muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB", "mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q", 1000) >>> sender_wallet = Wallet(network="testnet").from_passphrase("meherett") >>> fund_transaction = FundTransaction(network="testnet") >>> fund_transaction.build_transaction(wallet=sender_wallet, htlc=htlc, amount=10000) <shuttle.providers.bitcoin.transaction.FundTransaction object at 0x0409DAF0> """ # Checking parameter instances if not isinstance(wallet, Wallet): raise TypeError( "invalid wallet instance, only takes Bitcoin Wallet class") if not isinstance(htlc, HTLC): raise TypeError( "invalid htlc instance, only takes Bitcoin HTLC class") if not isinstance(amount, int): raise TypeError("invalid amount instance, only takes integer type") # Setting wallet, htlc, amount and unspent self.wallet, self.htlc, self.amount = wallet, htlc, amount # Getting unspent transaction output self.unspent = self.wallet.unspent() # Setting previous transaction indexes self.previous_transaction_indexes = \ self.get_previous_transaction_indexes(amount=self.amount) # Getting transaction inputs and amount inputs, amount = self.inputs( utxos=self.unspent, previous_transaction_indexes=self.previous_transaction_indexes) # Calculating Bitcoin fee self._fee = fee_calculator(len(inputs), 2) if amount < (self.amount + self._fee): raise BalanceError("insufficient spend utxos") # Building mutable Bitcoin transaction self.transaction = MutableTransaction( version=self.version, ins=inputs, outs=[ # Funding into hash time lock contract script hash TxOut(value=self.amount, n=0, script_pubkey=P2shScript.unhexlify( hex_string=self.htlc.hash())), # Controlling amounts when we are funding on htlc script TxOut(value=(amount - (self._fee + self.amount)), n=1, script_pubkey=P2pkhScript.unhexlify( hex_string=self.wallet.p2pkh())) ], locktime=Locktime(locktime)) self._type = "bitcoin_fund_unsigned" return self
def build_transaction( self, address: str, transaction_hash: str, locktime: int = config["locktime"]) -> "RefundTransaction": """ Build Bitcoin refund transaction. :param address: Bitcoin sender address. :type address: str :param transaction_hash: Bitcoin funded transaction hash/id. :type transaction_hash: str :param locktime: Bitcoin transaction lock time, defaults to ``0``. :type locktime: int :returns: RefundTransaction -- Bitcoin refund transaction instance. >>> from swap.providers.bitcoin.transaction import RefundTransaction >>> refund_transaction: RefundTransaction = RefundTransaction("testnet") >>> refund_transaction.build_transaction(address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", transaction_hash="a211d21110756b266925fee2fbf2dc81529beef5e410311b38578dc3a076fb31") <swap.providers.bitcoin.transaction.RefundTransaction object at 0x0409DAF0> """ # Check parameter instances if not is_address(address, self._network): raise AddressError( f"Invalid Bitcoin sender '{address}' {self._network} address.") # Set address and transaction_hash self._address, self._transaction_hash, = address, transaction_hash # Get transaction self._transaction_detail = get_transaction( transaction_hash=self._transaction_hash, network=self._network) # Find HTLC UTXO self._htlc_utxo = find_p2sh_utxo(transaction=self._transaction_detail) if self._htlc_utxo is None: raise ValueError( "Invalid transaction id, there is no pay to script hash (P2SH) address." ) self._amount = self._htlc_utxo["value"] # Calculate the fee self._fee = fee_calculator(1, 1) outputs: list = [ TxOut(value=(self._amount - self._fee), n=0, script_pubkey=get_address_hash(address=self._address, script=True)) ] # Build mutable transaction self._transaction = MutableTransaction( version=self._version, ins=[ TxIn(txid=self._transaction_hash, txout=self._htlc_utxo["position"], script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=outputs, locktime=Locktime(locktime)) # Set transaction type self._type = "bitcoin_refund_unsigned" return self
def build_transaction( self, address: str, htlc: HTLC, amount: Optional[Union[int, float]], unit: str = config["unit"], locktime: int = config["locktime"]) -> "FundTransaction": """ Build Bitcoin fund transaction. :param address: Bitcoin sender address. :type address: str :param htlc: Bitcoin HTLC instance. :type htlc: bitcoin.htlc.HTLC :param amount: Bitcoin amount, default to ``None``. :type amount: int, float :param unit: Bitcoin unit, default to ``Satoshi``. :type unit: str :param locktime: Bitcoin transaction lock time, defaults to ``0``. :type locktime: int :returns: FundTransaction -- Bitcoin fund transaction instance. >>> from swap.providers.bitcoin.htlc import HTLC >>> from swap.providers.bitcoin.transaction import FundTransaction >>> from swap.utils import sha256 >>> htlc: HTLC = HTLC(network="testnet") >>> htlc.build_htlc(secret_hash=sha256("Hello Meheret!"), recipient_address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", sender_address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", endtime=1624687630) >>> fund_transaction: FundTransaction = FundTransaction(network="testnet") >>> fund_transaction.build_transaction(address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", htlc=htlc, amount=0.001, unit="BTC") <swap.providers.bitcoin.transaction.FundTransaction object at 0x0409DAF0> """ # Check parameter instances if not is_address(address, self._network): raise AddressError( f"Invalid Bitcoin sender '{address}' {self._network} address.") if not isinstance(htlc, HTLC): raise TypeError( "Invalid Bitcoin HTLC instance, only takes xinfin HTLC class") if htlc.agreements and address != htlc.agreements["sender_address"]: raise AddressError( f"Wrong Bitcoin sender '{address}' address", "address must be equal with HTLC agreements sender address.") if unit not in ["BTC", "mBTC", "Satoshi"]: raise UnitError( "Invalid Bitcoin unit, choose only 'BTC', 'mBTC' or 'Satoshi' units." ) self._address, outputs, self._htlc, self._amount = ( address, [], htlc, (amount if unit == "Satoshi" else amount_unit_converter( amount=amount, unit_from=f"{unit}2Satoshi"))) # Get Sender UTXO's self._utxos = get_utxos(address=self._address, network=self._network) # Outputs action outputs.append( TxOut(value=self._amount, n=0, script_pubkey=get_address_hash( address=self._htlc.contract_address(), script=True))) # Get previous transaction indexes self._previous_transaction_indexes, max_amount = _get_previous_transaction_indexes( utxos=self._utxos, amount=self._amount, transaction_output=2) # Build transaction inputs inputs, amount = _build_inputs( utxos=self._utxos, previous_transaction_indexes=self._previous_transaction_indexes) # Calculate the fee self._fee = fee_calculator(len(inputs), 2) if amount < self._amount: raise BalanceError("Insufficient spend UTXO's", "you don't have enough amount.") elif amount < (self._amount + self._fee): raise BalanceError( f"You don't have enough amount to pay '{self._fee}' Satoshi fee", f"you can spend maximum '{amount - self._fee}' Satoshi amount." ) return_amount: int = int(amount - (self._amount + self._fee)) if return_amount != 0: outputs.append( TxOut(value=return_amount, n=len(outputs), script_pubkey=get_address_hash(address=self._address, script=True))) # Build mutable transaction self._transaction = MutableTransaction(version=self._version, ins=inputs, outs=outputs, locktime=Locktime(locktime)) # Set transaction type self._type = "bitcoin_fund_unsigned" return self
def commit_tx(committer, receiver, secret, type, lock_time, penalty): if type not in ['Timed', 'Height']: raise ValueError("type should be 'Timed' or 'Height'") secret_hash = hashlib.sha256(hashlib.sha256( secret.encode()).digest()).digest() secret_hash = StackData.from_bytes(secret_hash) print("秘密经hash256加密结果:", secret_hash) # 创建输出脚本 if type is 'Height': # Relative - HeightBased sequence = lock_time lock_time_script = IfElseScript( # if branch Hashlock256Script(secret_hash, P2pkhScript(committer.pubk)), # else branch RelativeTimelockScript( # timelocked script HeightBasedSequence(sequence), # expiration P2pkhScript(receiver.pubk))) else: # Relative - TimedBased time_delta = datetime.timedelta(minutes=lock_time) time_now = datetime.datetime.now() print("current time: ", time_now.strftime("%y-%m-%d %H:%M:%S")) print("pay deposit time: ", (time_now + time_delta).strftime("%y-%m-%d %H:%M:%S")) lock_time_script = IfElseScript( # if branch Hashlock256Script(secret_hash, P2pkhScript(committer.pubk)), # else branch RelativeTimelockScript( # timelocked script TimeBasedSequence.from_timedelta(time_delta), # expiration P2pkhScript(receiver.pubk))) # 找零脚本 change_script = P2pkhScript(committer.pubk) # 清理资产 print("sweeping fund...") to_spend_hash, balance = sweep_fund(privkey=str(committer.privk), address=str(committer.address), coin_symbol=coin_symbol, api_key=committer.api_key) # 估算挖矿费用 print('estimating mining fee...') mining_fee_per_kb = get_mining_fee_per_kb(coin_symbol, committer.api_key, condidence='high') estimated_tx_size = cal_tx_size_in_byte(inputs_num=1, outputs_num=2) mining_fee = int(mining_fee_per_kb * (estimated_tx_size / 1000)) * 2 # 设置罚金 assert penalty + mining_fee <= balance, 'committer账户余额不足' # 创建交易 to_spend_raw = get_raw_tx(to_spend_hash, coin_symbol) to_spend = TransactionFactory.unhexlify(to_spend_raw) unsigned = MutableTransaction(version=2, ins=[ TxIn(txid=to_spend.txid, txout=0, script_sig=ScriptSig.empty(), sequence=Sequence.max()) ], outs=[ TxOut(value=penalty, n=0, script_pubkey=lock_time_script), TxOut(value=balance - penalty - mining_fee, n=1, script_pubkey=change_script) ], locktime=Locktime(0)) # 输入脚本 solver = P2pkhSolver(committer.privk) # 修改交易 signed = unsigned.spend([to_spend.outs[0]], [solver]) print('commit_tx_hex: ', signed.hexlify()) # 发送交易 tx = pushtx(coin_symbol=coin_symbol, api_key=committer.api_key, tx_hex=signed.hexlify()) format_output(tx) return tx['tx']['hash']
def build_htlc(self, secret_hash: str, recipient_address: str, sender_address: str, endtime: int) -> "HTLC": """ Build Bitcoin Hash Time Lock Contract (HTLC). :param secret_hash: secret sha-256 hash. :type secret_hash: str :param recipient_address: Bitcoin recipient address. :type recipient_address: str :param sender_address: Bitcoin sender address. :type sender_address: str :param endtime: Expiration block time (Seconds). :type endtime: int :returns: HTLC -- Bitcoin Hash Time Lock Contract (HTLC) instance. >>> from swap.providers.bitcoin.htlc import HTLC >>> from swap.utils import sha256 >>> htlc: HTLC = HTLC(network="testnet") >>> htlc.build_htlc(secret_hash=sha256("Hello Meheret!"), recipient_address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", sender_address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", endtime=1624687630) <swap.providers.bitcoin.htlc.HTLC object at 0x0409DAF0> """ # Check parameter instances if len(secret_hash) != 64: raise ValueError("Invalid secret hash, length must be 64.") if not is_address(address=recipient_address, network=self._network): raise AddressError( f"Invalid Bitcoin recipient '{recipient_address}' {self._network} address." ) if not is_address(address=sender_address, network=self._network): raise AddressError( f"Invalid Bitcoin sender '{sender_address}' {self._network} address." ) # Get current working directory path (like linux or unix path). cwd: str = PurePosixPath(os.path.dirname( os.path.realpath(__file__))).__str__().replace("\\", "/") with open(f"{cwd}/contracts/htlc.script", "r", encoding="utf-8") as htlc_script: htlc_opcode: str = htlc_script.readlines()[ -1] # HTLC OP_Code script htlc_script.close() build_htlc_opcode: str = htlc_opcode.format( secret_hash=hashlib.sha256(unhexlify(secret_hash)).hexdigest(), recipient_address_hash=get_address_hash(address=recipient_address, script=False), sender_address_hash=get_address_hash(address=sender_address, script=False), endtime=Locktime(n=endtime).for_script().hexlify()[2:]) self.agreements = { "secret_hash": secret_hash, "recipient_address": recipient_address, "sender_address": sender_address, "endtime": { "datetime": str(datetime.fromtimestamp(endtime)), "timestamp": endtime } } bytecode: str = Script.compile(build_htlc_opcode) self._script = ScriptBuilder.identify(bytecode) return self
def __init__(self, *args, **kwargs): global keys super().__init__(*args, **kwargs) pubs = [ExtendedPublicKey.decode(pair[0]).key for pair in keys] privs = [ExtendedPrivateKey.decode(pair[1]).key for pair in keys] all_embedders = { 'p2sh', 'p2wsh', 'ifelse', 'absolutetime', 'relativetime', 'hash160', 'hash256' } self.scripts = [ { 'name': 'p2pkh', 'script': P2pkhScript(pubs[0]), 'solver': partial(P2pkhSolver, privs[0]), 'embeddable_by': all_embedders }, { 'name': 'p2wpkh', 'script': P2wpkhV0Script(pubs[1]), 'solver': partial(P2wpkhV0Solver, privs[1]), 'embeddable_by': {'p2sh'} }, { 'name': 'p2pk', 'script': P2pkScript(pubs[2]), 'solver': partial(P2pkSolver, privs[2]), 'embeddable_by': all_embedders }, { 'name': 'multisig', 'script': MultisigScript(2, pubs[3], pubs[4], pubs[5], 3), 'solver': partial(MultisigSolver, privs[3], privs[4]), 'embeddable_by': all_embedders }, ] self.sighashed_scripts = [] for script in self.scripts: for args in (('ALL', False), ('ALL', True), ('NONE', False), ('NONE', True), ('SINGLE', False), ('SINGLE', True)): scriptcpy = copy.deepcopy(script) scriptcpy['sighash'] = Sighash(*args) try: scriptcpy['solver'] = scriptcpy['solver'](sighash=Sighash( *args)) except TypeError: scriptcpy['solver'] = scriptcpy['solver']( sighashes=[Sighash( *args), Sighash(*args)]) self.sighashed_scripts.append(scriptcpy) self.scripts = self.sighashed_scripts self.scripts = self.sighashed_scripts self.final = { 'name': 'nulldata', 'script': NulldataScript(StackData.unhexlify('deadbeef')), 'solver': None, 'embeddable_by': {} } self.preimage_streams = [Stream(TestSpends.rand_bytes())] self.preimages = [pre.serialize() for pre in self.preimage_streams] self.hashes160 = [ preimage.hash160() for preimage in self.preimage_streams ] self.hashes256 = [ preimage.hash256() for preimage in self.preimage_streams ] self.all = [(s['script'], (s['solver'], s['script']), s['name']) for s in self.scripts] self.embedders = [ TimelockEmbedder, Relativetimelockembedder, Hashlock160Embedder, Hashlock256Embedder ] self.double_embedders = [IfElseEmbedder] embedded = [] for embedder in self.embedders: if embedder.get_name() != 'ifelse': for script in self.scripts: if embedder.get_name() in script['embeddable_by']: if embedder.get_name() == 'hash160': for preimage, phash in zip(self.preimages, self.hashes160): emb = embedder(phash, scripts=[script['script']]) embedded.append( (emb.instance, (HashlockSolver( preimage, script['solver']), emb.instance), '{}({})'.format(embedder.get_name(), script['name']))) elif embedder.get_name() == 'hash256': for preimage, phash in zip(self.preimages, self.hashes256): emb = embedder(phash, scripts=[script['script']]) embedded.append( (emb.instance, (HashlockSolver( preimage, script['solver']), emb.instance), '{}({})'.format(embedder.get_name(), script['name']))) elif embedder.get_name() == 'relativetime': emb = embedder(scripts=[script['script']]) embedded.append( (emb.instance, (RelativeTimelockSolver( Sequence(3), script['solver']), emb.instance), '{}({})'.format(embedder.get_name(), script['name']))) elif embedder.get_name() == 'absolutetime': emb = embedder(scripts=[script['script']]) embedded.append( (emb.instance, (AbsoluteTimelockSolver( Locktime(100), script['solver']), emb.instance), '{}({})'.format(embedder.get_name(), script['name']))) else: raise ValueError('Unknown embedder: {}'.format( embedder.get_name())) self.all += [s for s in embedded] for embedder in self.double_embedders: included = [(x, y, t) for (x, y, t) in self.all if t != 'p2wpkh'] for ((if_script, (if_solver, _), if_type), (else_script, (else_solver, _), else_type)) in TestSpends.pairwise(included): for branch in [Branch.IF, Branch.ELSE]: inst = embedder(scripts=[if_script, else_script]).instance # print(type(if_script), type(if_keys), type(if_spend), type(if_type)) # if 'hash' in else_type: # print(else_spend) self.all.append( (inst, (IfElseSolver( branch, if_solver if branch == Branch.IF else else_solver), inst), 'ifelse({}, {})'.format(if_type, else_type))) for script, (solver, _), stype in [s for s in self.all]: if 'p2wpkh' not in stype: inst = P2wshEmbedder(scripts=[script]).instance self.all.append((inst, (P2wshV0Solver(script, solver), script), 'p2wsh({})'.format(stype))) for script, (solver, prev), stype in [s for s in self.all]: inst = P2shEmbedder(scripts=[script]).instance self.all.append( (inst, (P2shSolver(script, solver), prev), 'p2sh({})'.format(stype)))