def expiration_to_script(sequence): if isinstance(sequence, int): if sequence <= 16: return "OP_%d" % sequence else: return Sequence(sequence).for_script() raise TypeError("Sequence must be integer format!")
def select_inputs(self, total_amount, address=None): '''finds apropriate utxo's to include in rawtx, while being careful to never spend old transactions with a lot of coin age. Argument is intiger, returns list of apropriate UTXO's''' utxos = [] utxo_sum = Decimal(-0.01) # starts from negative due to minimal fee for tx in sorted(self.listunspent(address=address), key=itemgetter('confirmations')): if tx["address"] not in (self.pa_parameters.P2TH_addr, self.pa_parameters.test_P2TH_addr): utxos.append( MutableTxIn(txid=tx['txid'], txout=tx['vout'], sequence=Sequence.max(), script_sig=ScriptSig.empty())) utxo_sum += Decimal(tx["amount"]) if utxo_sum >= total_amount: return {'utxos': utxos, 'total': utxo_sum} if utxo_sum < total_amount: raise InsufficientFunds("Insufficient funds.")
def select_inputs(self, address: str, amount: int) -> dict: '''finds apropriate utxo's to include in rawtx, while being careful to never spend old transactions with a lot of coin age. Argument is intiger, returns list of apropriate UTXO's''' utxos = [] utxo_sum = Decimal(0) for tx in sorted(self.listunspent(address=address), key=itemgetter('confirmations')): if tx["address"] not in (self.pa_parameters.P2TH_addr, self.pa_parameters.test_P2TH_addr): utxos.append( MutableTxIn(txid=tx['txid'], txout=tx['vout'], sequence=Sequence.max(), script_sig=ScriptSig.empty())) utxo_sum += Decimal(tx["amount"]) if utxo_sum >= amount: return {'utxos': utxos, 'total': utxo_sum} if utxo_sum < amount: raise InsufficientFunds("Insufficient funds.") raise Exception("undefined behavior :.(")
def init(self, secret_hash, recipient_address, sender_address, sequence=bitcoin["sequence"]): """ Initialize 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 sequence: Bitcoin sequence number of expiration block, defaults to 1000. :type sequence: int :returns: HTLC -- Bitcoin Hash Time Lock Contract (HTLC) instance. >>> from swap.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc.init(secret_hash="3a26da82ead15a80533a02696656b14b5dbfd84eb14790f2e1be5e9e45820eeb", recipient_address="muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB", sender_address="mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q", sequence=1000) <swap.providers.bitcoin.htlc.HTLC object at 0x0409DAF0> """ # Checking parameter instances if not isinstance(secret_hash, str): raise TypeError("secret hash must be string format") if len(secret_hash) != 64: raise ValueError("invalid secret hash, length must be 64.") if not isinstance(recipient_address, str): raise TypeError("recipient address must be string format") if not is_address(recipient_address, self.network): raise AddressError("invalid %s recipient %s address" % (self.network, recipient_address)) if not isinstance(sender_address, str): raise TypeError("sender address must be string format") if not is_address(sender_address, self.network): raise AddressError("invalid %s sender %s address" % (self.network, sender_address)) if not isinstance(sequence, int): raise TypeError("sequence must be integer format") # HASH TIME LOCK CONTRACT SCRIPT self.script = IfElseScript( # If branch Hashlock256Script( # Hash lock 250 hashlib.sha256(unhexlify(secret_hash)).digest( ), # Secret double hash for (OP_HASH256) script_from_address( address=recipient_address, network=self.network) # Script hash of account two ), # Else branch RelativeTimelockScript( # Relative time locked script Sequence(sequence), # Expiration blocks script_from_address( address=sender_address, network=self.network) # Script hash of account one )) return self
def init(self, secret_hash, recipient_address, sender_address, sequence=bitcoin["sequence"]): """ Initialize Bitcoin Hash Time Lock Contract (HTLC). :param secret_hash: secret sha-256 hash. :type secret_hash: hash :param recipient_address: Bitcoin recipient address. :type recipient_address: str :param sender_address: Bitcoin sender address. :type sender_address: str :param sequence: Bitcoin sequence number of expiration block, defaults to Bitcoin config sequence (15). :type sequence: int :returns: HTLC -- Bitcoin Hash Time Lock Contract (HTLC) instance. >>> from shuttle.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc.init(secret_hash, recipient_address, sender_address, 100) <shuttle.providers.bitcoin.htlc.HTLC object at 0x0409DAF0> """ # Checking parameter instances if not isinstance(secret_hash, str): raise TypeError("secret hash must be string format") if len(secret_hash) != 64: raise ValueError("invalid secret hash, length must be 64.") if not isinstance(recipient_address, str): raise TypeError("recipient address must be string format") if not is_address(recipient_address, self.network): raise AddressError("invalid %s recipient %s address" % (self.network, recipient_address)) if not isinstance(sender_address, str): raise TypeError("sender address must be string format") if not is_address(sender_address, self.network): raise AddressError("invalid %s sender %s address" % (self.network, sender_address)) if not isinstance(sequence, int): raise TypeError("sequence must be integer format") # HASH TIME LOCK CONTRACT SCRIPT self.script = IfElseScript( # If branch Hashlock256Script( # Hash lock 250 sha256(unhexlify(secret_hash)), # Secret key script_from_address( address=recipient_address, network=self.network) # Script hash of account two ), # Else branch RelativeTimelockScript( # Relative time locked script Sequence(sequence), # Expiration blocks script_from_address( address=sender_address, network=self.network) # Script hash of account one )) return self
def inputs(utxos, previous_transaction_indexes=None): inputs, amount = list(), int() for index, utxo in enumerate(utxos): if previous_transaction_indexes is None or index in previous_transaction_indexes: amount += utxo["amount"] inputs.append( TxIn(txid=utxo["hash"], txout=utxo["output_index"], script_sig=ScriptSig.empty(), sequence=Sequence.max())) return inputs, amount
def tx_in(tx, inscript): #TODO change from 1 i/p to multi i/p to_spend = Transaction.unhexlify(tx) intxs = [ TxIn(txid=to_spend.txid, txout=0, sequence=Sequence.max(), script_sig=inscript) ] #ScriptSig.<something> return intxs
def solve(self, network: str = config["network"]) -> IfElseSolver: return IfElseSolver( branch=Branch.ELSE, inner_solver=RelativeTimelockSolver( sequence=Sequence(seq=self._sequence), inner_solver=P2pkhSolver(privk=PrivateKey.unhexlify( hexa=Wallet(network=network).from_root_xprivate_key( root_xprivate_key=self._root_xprivate_key).from_path( path=self._path).private_key()))))
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 createContractInput(fundingTx): to_spend = fundingTx #TODO Failing here #Signer=fundingTxn() #Todo LATEST Failing here .Change to spend input Transaction Signer = ScriptSig.empty() #TOdo create SignFundingTx(FundingTx, Solver) SigScript = [ MutableTxIn( txid=to_spend, # Input need not be a segwit transaction. txout=0, script_sig=Signer, sequence=Sequence.max()) ] return SigScript
def _build_inputs( utxos: list, previous_transaction_indexes: Optional[list] = None) -> tuple: inputs, amount = [], 0 for index, utxo in enumerate(utxos): if previous_transaction_indexes is None or index in previous_transaction_indexes: amount += utxo["value"] inputs.append( TxIn(txid=utxo["tx_hash"], txout=utxo["tx_output_n"], script_sig=ScriptSig.empty(), sequence=Sequence.max())) return inputs, amount
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 build_htlc(self, secret_hash: str, recipient_address: str, sender_address: str, sequence: int = config["sequence"]) -> "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 sequence: Bitcoin sequence number of expiration block, defaults to 1000. :type sequence: int :returns: HTLC -- Bitcoin Hash Time Lock Contract (HTLC) instance. >>> from swap.providers.bitcoin.htlc import HTLC >>> from swap.utils import sha256 >>> htlc = HTLC(network="testnet") >>> htlc.build_htlc(secret_hash=sha256("Hello Meheret!"), recipient_address="mgokpSJoX7npmAK1Zj8ze1926CLxYDt1iF", sender_address="mkFWGt4hT11XS8dJKzzRFsTrqjjAwZfQAC", sequence=1000) <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." ) self._script = IfElseScript( Hashlock256Script( hashlib.sha256(unhexlify(secret_hash)).digest(), get_address_hash(address=recipient_address, script=True)), RelativeTimelockScript( Sequence(sequence), get_address_hash(address=sender_address, script=True))) return self
def select_inputs(self, address: str, amount: int) -> dict: utxos = [] utxo_sum = Decimal(-0.01) # starts from negative due to minimal fee for tx in self.listunspent(address=address): utxos.append( TxIn(txid=tx['tx_hash'], txout=tx['tx_ouput_n'], sequence=Sequence.max(), script_sig=ScriptSig.empty()) ) utxo_sum += Decimal(tx['value'] / 10**8) if utxo_sum >= amount: return {'utxos': utxos, 'total': utxo_sum} if utxo_sum < amount: raise InsufficientFunds('Insufficient funds.')
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 select_inputs(self, address: str, amount: int) -> dict: utxos = [] utxo_sum = Decimal(-0.01) # starts from negative due to minimal fee for tx in self.listunspent(address=address): script = self.getrawtransaction( tx['txid'])['vout'][0]['scriptPubKey']['hex'] utxos.append( TxIn(txid=tx['txid'], txout=tx['vout'], sequence=Sequence.max(), script_sig=ScriptSig.unhexlify(script))) utxo_sum += Decimal(tx['amount']) if utxo_sum >= amount: return {'utxos': utxos, 'total': utxo_sum} if utxo_sum < amount: raise InsufficientFunds('Insufficient funds.') raise Exception("undefined behavior :.(")
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 select_inputs(self, address: str, amount: int) -> dict: '''select UTXOs''' utxos = [] utxo_sum = Decimal(-0.01) # starts from negative due to minimal fee for tx in sorted(self.listunspent(address=address), key=itemgetter('confirmations')): utxos.append( TxIn(txid=tx['tx_hash'], txout=tx['tx_ouput_n'], sequence=Sequence.max(), script_sig=ScriptSig.unhexlify(tx['script'])) ) utxo_sum += Decimal(int(tx['value']) / 100000000) if utxo_sum >= amount: return {'utxos': utxos, 'total': utxo_sum} if utxo_sum < amount: raise InsufficientFunds('Insufficient funds.') raise Exception("undefined behavior :.(")
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 __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)))
mining_fee = int(mining_fee_per_kb * (estimated_tx_size / 1000)) * 2 # 设置罚金 penalty = 100000 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(privk) # 修改交易 signed = unsigned.spend([to_spend.outs[0]], [solver])
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 solve(self): return IfElseSolver( Branch.ELSE, RelativeTimelockSolver(Sequence(self.sequence), P2pkhSolver(self.private_key)))
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_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 get_args(): return Sequence(3),
def solve(self): return IfElseSolver(branch=Branch.ELSE, inner_solver=RelativeTimelockSolver( sequence=Sequence(self.sequence), inner_solver=P2pkhSolver(self.private_key)))
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 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