def _get_signed_txn(self, wif_keys): """ :param wif_keys: list of wif keys corresponding with self.input_addresses addresses, in same order """ # NB: they are not guaranteed to be in order as there # may be more than one utxo associated with a single # address, but there will always be 1 solver associated # a private key unordered_solvers = [] unordered_tx_outs = [] unsigned = self._txn if self.is_signed: raise ValueError('cannot sign txn (already signed)') for key in wif_keys: # create btcpy PrivateKeys from input WIF format keys private_key = PrivateKey.from_wif(key) if self.is_segwit: pub_key = private_key.pub(compressed=True) s_solver = P2shSolver( P2wpkhV0Script(pub_key), P2wpkhV0Solver(private_key) ) unordered_solvers.append(s_solver) else: # create btcpy P2PKH Solvers from those PrivateKeys unordered_solvers.append(P2pkhSolver(private_key)) # a dict that matches the addresses (which are ordered the same as # their above WIF Keys) to their solvers addresses_solvers = dict(zip(self.input_addresses, unordered_solvers)) # from self._specific_utxo_data, take the output num, value and scriptPubKey # and create TxOuts representing the UTXO's that will be spent. # In a tuple with the address of the UTXO so the correct solver # can be found later for t in self._specific_utxo_data: unordered_tx_outs.append((t[2], TxOut(value=t[4], n=t[1], script_pubkey=Script.unhexlify(t[3])))) # unlike the lists defined at the top of the method, these are in # order i.e the solver in solvers[0] is the solver for the TxOut of # tx_outs[0]. this is required to pass them into the spend() method tx_outs = [] solvers = [] for t in unordered_tx_outs: address = t[0] tx_outs.append(t[1]) solvers.append(addresses_solvers[address]) signed = unsigned.spend(tx_outs, solvers) return signed
def outputs(utxos, previous_transaction_indexes=None): outputs = list() for index, utxo in enumerate(utxos): if previous_transaction_indexes is None or index in previous_transaction_indexes: outputs.append( TxOut(value=utxo["amount"], n=utxo["output_index"], script_pubkey=Script.unhexlify(utxo["script"]))) return outputs
def _build_outputs(utxos: list, previous_transaction_indexes: Optional[list] = None, only_dict: bool = False) -> list: outputs = [] for index, utxo in enumerate(utxos): if previous_transaction_indexes is None or index in previous_transaction_indexes: outputs.append( TxOut(value=utxo["value"], n=utxo["tx_output_n"], script_pubkey=Script.unhexlify( hex_string=utxo["script"])) if not only_dict else dict(value=utxo["value"], tx_output_n=utxo["tx_output_n"], script=utxo["script"])) return outputs
def from_opcode(self, opcode): """ Initiate bitcoin Hash Time Lock Contract (HTLC) from opcode script. :param opcode: Bitcoin opcode script. :type opcode: str. :returns: HTLC -- bitcoin Hash Time Lock Contract (HTLC) instance. >>> from shuttle.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc.from_opcode(htlc_opcode_script) <shuttle.providers.bitcoin.htlc.HTLC object at 0x0409DAF0> """ if isinstance(opcode, str): bytecode = Script.compile(opcode) self.script = ScriptBuilder.identify(bytecode) return self raise TypeError("op_code must be string format")
def from_opcode(self, opcode: str) -> "HTLC": """ Initiate Bitcoin Hash Time Lock Contract (HTLC) from opcode script. :param opcode: Bitcoin opcode script. :type opcode: str :returns: HTLC -- Bitcoin Hash Time Lock Contract (HTLC) instance. >>> from swap.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> opcode = "OP_IF OP_HASH256 821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e0158 OP_EQUALVERIFY OP_DUP OP_HASH160 0e259e08f2ec9fc99a92b6f66fdfcb3c7914fd68 OP_EQUALVERIFY OP_CHECKSIG OP_ELSE e803 OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 33ecab3d67f0e2bde43e52f41ec1ecbdc73f11f8 OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF" >>> htlc.from_opcode(opcode=opcode) <swap.providers.bitcoin.htlc.HTLC object at 0x0409DAF0> """ bytecode = Script.compile(opcode) self._script = ScriptBuilder.identify(bytecode) return self
def from_opcode(self, opcode: str) -> "HTLC": """ Initiate Bitcoin Hash Time Lock Contract (HTLC) from opcode script. :param opcode: Bitcoin opcode script. :type opcode: str :returns: HTLC -- Bitcoin Hash Time Lock Contract (HTLC) instance. >>> from swap.providers.bitcoin.htlc import HTLC >>> htlc: HTLC = HTLC(network="testnet") >>> opcode: str = "OP_IF OP_HASH256 821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e0158 OP_EQUALVERIFY OP_DUP OP_HASH160 0a0a6590e6ba4b48118d21b86812615219ece76b OP_EQUALVERIFY OP_CHECKSIG OP_ELSE 0ec4d660 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 e00ff2a640b7ce2d336860739169487a57f84b15 OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF" >>> htlc.from_opcode(opcode=opcode) <swap.providers.bitcoin.htlc.HTLC object at 0x0409DAF0> """ bytecode = Script.compile(opcode) self._script = ScriptBuilder.identify(bytecode) return self
def from_opcode(self, opcode): """ Initiate Bitcoin Hash Time Lock Contract (HTLC) from opcode script. :param opcode: Bitcoin opcode script. :type opcode: str :returns: HTLC -- Bitcoin Hash Time Lock Contract (HTLC) instance. >>> from swap.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc_opcode_script = "OP_IF OP_HASH256 821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e0158 OP_EQUALVERIFY OP_DUP OP_HASH160 98f879fb7f8b4951dee9bc8a0327b792fbe332b8 OP_EQUALVERIFY OP_CHECKSIG OP_ELSE e803 OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 64a8390b0b1685fcbf2d4b457118dc8da92d5534 OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF" >>> htlc.from_opcode(opcode=htlc_opcode_script) <swap.providers.bitcoin.htlc.HTLC object at 0x0409DAF0> """ if isinstance(opcode, str): bytecode = Script.compile(opcode) self.script = ScriptBuilder.identify(bytecode) return self raise TypeError("op_code must be string format")
def sign(self, unsigned_raw, solver): """ Sign unsigned fund transaction raw. :param unsigned_raw: bitcoin unsigned fund transaction raw. :type unsigned_raw: str :param solver: bitcoin fund solver. :type solver: bitcoin.solver.FundSolver :returns: FundSignature -- bitcoin fund signature instance. >>> from shuttle.providers.bitcoin.signature import FundSignature >>> fund_signature = FundSignature() >>> fund_signature.sign(bitcoin_fund_unsigned, fund_solver) <shuttle.providers.bitcoin.signature.FundSignature object at 0x0409DAF0> """ tx_raw = json.loads(b64decode(str(unsigned_raw).encode()).decode()) if "raw" not in tx_raw or "outputs" not in tx_raw or "type" not in tx_raw or "fee" not in tx_raw: raise ValueError("invalid unsigned fund transaction raw") self.fee = tx_raw["fee"] self.type = tx_raw["type"] if not self.type == "bitcoin_fund_unsigned": raise TypeError("can't sign this %s transaction using FundSignature" % tx_raw["type"]) if not isinstance(solver, FundSolver): raise TypeError("invalid solver instance, only takes bitcoin FundSolver class") self.transaction = MutableTransaction.unhexlify(tx_raw["raw"]) outputs = list() for output in tx_raw["outputs"]: outputs.append( TxOut(value=output["amount"], n=output["n"], script_pubkey=Script.unhexlify(output["script"]))) self.transaction.spend(outputs, [solver.solve() for _ in outputs]) self.signed = b64encode(str(json.dumps(dict( raw=self.transaction.hexlify(), fee=tx_raw["fee"], network=tx_raw["network"], type="bitcoin_fund_signed" ))).encode()).decode() return self
def sign(self, unsigned_raw, solver): """ Sign unsigned fund transaction raw. :param unsigned_raw: Bitcoin unsigned fund transaction raw. :type unsigned_raw: str :param solver: Bitcoin fund solver. :type solver: bitcoin.solver.FundSolver :returns: FundSignature -- Bitcoin fund signature instance. >>> from shuttle.providers.bitcoin.signature import FundSignature >>> from shuttle.providers.bitcoin.solver import FundSolver >>> bitcoin_fund_unsigned_raw = "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMTg4OGJlN2VjMDY1MDk3ZDk1NjY0NzYzZjI3NmQ0MjU1NTJkNzM1ZmIxZDk3NGFlNzhiZjcyMTA2ZGNhMGYzOTEwMTAwMDAwMDAwZmZmZmZmZmYwMjEwMjcwMDAwMDAwMDAwMDAxN2E5MTQyYmIwMTNjM2U0YmViMDg0MjFkZWRjZjgxNWNiNjVhNWMzODgxNzhiODdiY2RkMGUwMDAwMDAwMDAwMTk3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJhbW91bnQiOiA5ODQ5NDYsICJuIjogMSwgInNjcmlwdCI6ICI3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9" >>> fund_solver = FundSolver("92cbbc5990cb5090326a76feeb321cad01048635afe5756523bbf9f7a75bf38b") >>> fund_signature = FundSignature(network="testnet") >>> fund_signature.sign(bitcoin_fund_unsigned_raw, fund_solver) <shuttle.providers.bitcoin.signature.FundSignature object at 0x0409DAF0> """ # Decoding and loading refund transaction fund_transaction = json.loads( b64decode( str(unsigned_raw + "=" * (-len(unsigned_raw) % 4)).encode()).decode()) # Checking refund transaction keys for key in ["raw", "outputs", "type", "fee", "network"]: if key not in fund_transaction: raise ValueError( "invalid Bitcoin unsigned fund transaction raw") if not fund_transaction["type"] == "bitcoin_fund_unsigned": raise TypeError( f"invalid Bitcoin fund unsigned transaction type, " f"you can't sign this {fund_transaction['type']} type by using FundSignature" ) if not isinstance(solver, FundSolver): raise TypeError( "invalid Bitcoin solver, it's only takes Bitcoin FundSolver class" ) # Setting transaction fee, type, network and transaction self._fee, self._type, self.network, self.transaction = ( fund_transaction["fee"], fund_transaction["type"], fund_transaction["network"], MutableTransaction.unhexlify(fund_transaction["raw"])) # Organizing outputs outputs = [] for output in fund_transaction["outputs"]: outputs.append( TxOut(value=output["amount"], n=output["n"], script_pubkey=Script.unhexlify( hex_string=output["script"]))) # Signing fund transaction self.transaction.spend(outputs, [solver.solve() for _ in outputs]) # Encoding fund transaction raw self._type = "bitcoin_fund_signed" self._signed_raw = b64encode( str( json.dumps( dict(raw=self.transaction.hexlify(), fee=fund_transaction["fee"], network=fund_transaction["network"], type=self._type))).encode()).decode() return self
def sign(self, transaction_raw: str, solver: FundSolver) -> "FundSignature": """ Sign unsigned fund transaction raw. :param transaction_raw: Bitcoin unsigned fund transaction raw. :type transaction_raw: str :param solver: Bitcoin fund solver. :type solver: bitcoin.solver.FundSolver :returns: FundSignature -- Bitcoin fund signature instance. >>> from swap.providers.bitcoin.signature import FundSignature >>> from swap.providers.bitcoin.solver import FundSolver >>> unsigned_fund_transaction_raw: str = "eyJmZWUiOiAxMTIyLCAicmF3IjogIjAyMDAwMDAwMDIzMWZiNzZhMGMzOGQ1NzM4MWIzMTEwZTRmNWVlOWI1MjgxZGNmMmZiZTJmZTI1NjkyNjZiNzUxMDExZDIxMWEyMDEwMDAwMDAwMGZmZmZmZmZmMDgwYjgyZWVjMzMyOTk2YTQyMmFlNGYwODBmNzRlNTNmZDJjYTRmMDcwMTFkNDdjNTkwODUzZTFlMzA1ZmUxMTAxMDAwMDAwMDBmZmZmZmZmZjAyYTA4NjAxMDAwMDAwMDAwMDE3YTkxNGM4Yzc3YTliNDNlZTJiZGYxYTA3YzQ4Njk5ODMzZDc2NjhiZjI2NGM4NzMyOWMwZDAwMDAwMDAwMDAxOTc2YTkxNGUwMGZmMmE2NDBiN2NlMmQzMzY4NjA3MzkxNjk0ODdhNTdmODRiMTU4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IFt7InZhbHVlIjogOTQzMzAsICJ0eF9vdXRwdXRfbiI6IDEsICJzY3JpcHQiOiAiNzZhOTE0ZTAwZmYyYTY0MGI3Y2UyZDMzNjg2MDczOTE2OTQ4N2E1N2Y4NGIxNTg4YWMifSwgeyJ2YWx1ZSI6IDg5ODc0NiwgInR4X291dHB1dF9uIjogMSwgInNjcmlwdCI6ICI3NmE5MTRlMDBmZjJhNjQwYjdjZTJkMzM2ODYwNzM5MTY5NDg3YTU3Zjg0YjE1ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9" >>> fund_solver: FundSolver = FundSolver(xprivate_key="tprv8ZgxMBicQKsPeMHMJAc6uWGYiGqi1MVM2ybmzXL2TAoDpQe85uyDpdT7mv7Nhdu5rTCBEKLZsd9KyP2LQZJzZTvgVQvENArgU8e6DoYBiXf") >>> fund_signature: FundSignature = FundSignature(network="testnet") >>> fund_signature.sign(transaction_raw=unsigned_fund_transaction_raw, solver=fund_solver) <swap.providers.bitcoin.signature.FundSignature object at 0x0409DAF0> """ if not is_transaction_raw(transaction_raw=transaction_raw): raise TransactionRawError("Invalid Bitcoin unsigned transaction raw.") transaction_raw = clean_transaction_raw(transaction_raw) decoded_transaction_raw = b64decode(transaction_raw.encode()) loaded_transaction_raw = json.loads(decoded_transaction_raw.decode()) if not loaded_transaction_raw["type"] == "bitcoin_fund_unsigned": raise TypeError(f"Invalid Bitcoin fund unsigned transaction raw type, " f"you can't sign {loaded_transaction_raw['type']} type by using fund signature.") # Check parameter instances if not isinstance(solver, FundSolver): raise TypeError(f"Solver must be Bitcoin FundSolver, not {type(solver).__name__} type.") # Set transaction fee, type, network and transaction self._fee, self._type, self._network, self._transaction = ( loaded_transaction_raw["fee"], loaded_transaction_raw["type"], loaded_transaction_raw["network"], MutableTransaction.unhexlify(loaded_transaction_raw["raw"]) ) # Organize outputs outputs = [] for output in loaded_transaction_raw["outputs"]: outputs.append(TxOut( value=output["value"], n=output["tx_output_n"], script_pubkey=Script.unhexlify( hex_string=output["script"] ) )) # Sign fund transaction self._transaction.spend( txouts=outputs, solvers=[solver.solve(network=self._network) for _ in outputs] ) # Encode fund transaction raw self._type = "bitcoin_fund_signed" self._signed_raw = b64encode(str(json.dumps(dict( raw=self._transaction.hexlify(), fee=self._fee, network=self._network, type=self._type ))).encode()).decode() return self
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 sign(self, transaction_raw: str, solver: NormalSolver) -> "NormalSignature": """ Sign unsigned normal transaction raw. :param transaction_raw: Bitcoin unsigned normal transaction raw. :type transaction_raw: str :param solver: Bitcoin normal solver. :type solver: bitcoin.solver.NormalSolver :returns: NormalSignature -- Bitcoin normal signature instance. >>> from swap.providers.bitcoin.signature import NormalSignature >>> from swap.providers.bitcoin.solver import NormalSolver >>> unsigned_normal_transaction_raw = "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMTA4MjVlMDBiYTU5NmFiMTExMjZjZDg5MjAzYjg4MmJjZTYwYTdkYjAxOWU1MTIxNzA1NmM0NzFmNTEwY2ZkODUwMDAwMDAwMDAwZmZmZmZmZmYwMjEwMjcwMDAwMDAwMDAwMDAxN2E5MTQ0Njk1MTI3YjFkMTdjNDU0ZjRiYWU5YzQxY2I4ZTNjZGI1ZTg5ZDI0ODdlYTVjMDEwMDAwMDAwMDAwMTk3NmE5MTQzM2VjYWIzZDY3ZjBlMmJkZTQzZTUyZjQxZWMxZWNiZGM3M2YxMWY4ODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJ2YWx1ZSI6IDEwMDAwMCwgInR4X291dHB1dF9uIjogMCwgInNjcmlwdCI6ICI3NmE5MTQzM2VjYWIzZDY3ZjBlMmJkZTQzZTUyZjQxZWMxZWNiZGM3M2YxMWY4ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9" >>> normal_solver: NormalSolver = NormalSolver(xprivate_key="tprv8ZgxMBicQKsPeMHMJAc6uWGYiGqi1MVM2ybmzXL2TAoDpQe85uyDpdT7mv7Nhdu5rTCBEKLZsd9KyP2LQZJzZTvgVQvENArgU8e6DoYBiXf") >>> normal_signature: NormalSignature = NormalSignature(network="testnet") >>> normal_signature.sign(transaction_raw=unsigned_normal_transaction_raw, solver=normal_solver) <swap.providers.bitcoin.signature.NormalSignature object at 0x0409DAF0> """ if not is_transaction_raw(transaction_raw=transaction_raw): raise TransactionRawError( "Invalid Bitcoin unsigned transaction raw.") transaction_raw = clean_transaction_raw(transaction_raw) decoded_transaction_raw = b64decode(transaction_raw.encode()) loaded_transaction_raw = json.loads(decoded_transaction_raw.decode()) if not loaded_transaction_raw["type"] == "bitcoin_normal_unsigned": raise TypeError( f"Invalid Bitcoin normal unsigned transaction raw type, " f"you can't sign {loaded_transaction_raw['type']} type by using normal signature." ) # Check parameter instances if not isinstance(solver, NormalSolver): raise TypeError( f"Solver must be Bitcoin NormalSolver, not {type(solver).__name__} type." ) # Set transaction fee, type, network and transaction self._fee, self._type, self._network, self._transaction = ( loaded_transaction_raw["fee"], loaded_transaction_raw["type"], loaded_transaction_raw["network"], MutableTransaction.unhexlify(loaded_transaction_raw["raw"])) # Organize outputs outputs = [] for output in loaded_transaction_raw["outputs"]: outputs.append( TxOut(value=output["value"], n=output["tx_output_n"], script_pubkey=Script.unhexlify( hex_string=output["script"]))) # Sign normal transaction self._transaction.spend( txouts=outputs, solvers=[solver.solve(network=self._network) for _ in outputs]) # Encode normal transaction raw self._type = "bitcoin_normal_signed" self._signed_raw = b64encode( str( json.dumps( dict(raw=self._transaction.hexlify(), fee=self._fee, network=self._network, type=self._type))).encode()).decode() return self
def sign(self, transaction_raw: str, solver: FundSolver) -> "FundSignature": """ Sign unsigned fund transaction raw. :param transaction_raw: Bitcoin unsigned fund transaction raw. :type transaction_raw: str :param solver: Bitcoin fund solver. :type solver: bitcoin.solver.FundSolver :returns: FundSignature -- Bitcoin fund signature instance. >>> from swap.providers.bitcoin.signature import Signature >>> from swap.providers.bitcoin.solver import FundSolver >>> unsigned_fund_transaction_raw = "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMTA4MjVlMDBiYTU5NmFiMTExMjZjZDg5MjAzYjg4MmJjZTYwYTdkYjAxOWU1MTIxNzA1NmM0NzFmNTEwY2ZkODUwMDAwMDAwMDAwZmZmZmZmZmYwMjEwMjcwMDAwMDAwMDAwMDAxN2E5MTQ0Njk1MTI3YjFkMTdjNDU0ZjRiYWU5YzQxY2I4ZTNjZGI1ZTg5ZDI0ODdlYTVjMDEwMDAwMDAwMDAwMTk3NmE5MTQzM2VjYWIzZDY3ZjBlMmJkZTQzZTUyZjQxZWMxZWNiZGM3M2YxMWY4ODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJ2YWx1ZSI6IDEwMDAwMCwgInR4X291dHB1dF9uIjogMCwgInNjcmlwdCI6ICI3NmE5MTQzM2VjYWIzZDY3ZjBlMmJkZTQzZTUyZjQxZWMxZWNiZGM3M2YxMWY4ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9" >>> sender_root_xprivate_key = "xprv9s21ZrQH143K3XihXQBN8Uar2WBtrjSzK2oRDEGQ25pA2kKAADoQXaiiVXht163ZTrdtTXfM4GqNRE9gWQHky25BpvBQuuhNCM3SKwWTPNJ" >>> fund_solver = FundSolver(sender_root_xprivate_key) >>> fund_signature = FundSignature("testnet") >>> fund_signature.sign(unsigned_fund_transaction_raw, fund_solver) <swap.providers.bitcoin.signature.FundSignature object at 0x0409DAF0> """ if not is_transaction_raw(transaction_raw=transaction_raw): raise TransactionRawError("Invalid Bitcoin unsigned transaction raw.") transaction_raw = clean_transaction_raw(transaction_raw) decoded_transaction_raw = b64decode(transaction_raw.encode()) loaded_transaction_raw = json.loads(decoded_transaction_raw.decode()) if not loaded_transaction_raw["type"] == "bitcoin_fund_unsigned": raise TypeError(f"Invalid Bitcoin fund unsigned transaction raw type, " f"you can't sign {loaded_transaction_raw['type']} type by using fund signature.") # Check parameter instances if not isinstance(solver, FundSolver): raise TypeError(f"Solver must be Bitcoin FundSolver, not {type(solver).__name__} type.") # Set transaction fee, type, network and transaction self._fee, self._type, self._datas, self._network, self._transaction = ( loaded_transaction_raw["fee"], loaded_transaction_raw["type"], loaded_transaction_raw["datas"], loaded_transaction_raw["network"], MutableTransaction.unhexlify(loaded_transaction_raw["raw"]) ) # Organize outputs outputs = [] for output in loaded_transaction_raw["outputs"]: outputs.append(TxOut( value=output["value"], n=output["tx_output_n"], script_pubkey=Script.unhexlify( hex_string=output["script"] ) )) # Sign fund transaction self._transaction.spend( txouts=outputs, solvers=[solver.solve(network=self._network) for _ in outputs] ) # Encode fund transaction raw self._type = "bitcoin_fund_signed" self._signed_raw = b64encode(str(json.dumps(dict( raw=self._transaction.hexlify(), fee=self._fee, network=self._network, type=self._type, datas=self._datas ))).encode()).decode() return self