def sign(self, solver): """ Sign bitcoin refund transaction. :param solver: bitcoin refund solver. :type solver: bitcoin.solver.RefundSolver :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) >>> refund_transaction.sign(refund_solver) <shuttle.providers.bitcoin.transaction.RefundTransaction object at 0x0409DAF0> """ if not isinstance(solver, RefundSolver): raise TypeError( "invalid solver instance, only takes bitcoin RefundSolver class" ) if self.transaction is None: raise ValueError( "transaction script is none, build transaction first") htlc = HTLC(self.network).init( secret_hash=sha256(solver.secret).hex(), recipient_address=str(self.wallet.address()), sender_address=str(self.sender_account.address()), sequence=solver.sequence) self.transaction.spend([ TxOut(value=self.htlc["value"], n=0, script_pubkey=P2shScript.unhexlify(self.htlc["script"])) ], [P2shSolver(htlc.script, solver.solve())]) return self
def get_address_hash( address: str, script: bool = False) -> Union[str, P2pkhScript, P2shScript]: """ Get Bitcoin address hash. :param address: Bitcoin address. :type address: str :param script: Return script (P2pkhScript, P2shScript), default to False. :type script: bool :returns: str -- Bitcoin address hash. >>> from swap.providers.bitcoin.utils import get_address_hash >>> get_address_hash(address="mrmtGq2HMmqAogSsGDjCtXUpxrb7rHThFH", script=False) "7b7c4431a43b612a72f8229935c469f1f6903658" """ if not is_address(address=address): raise AddressError(f"Invalid Bitcoin '{address}' address.") loaded_address = Address.from_string(address) get_type = loaded_address.get_type() if not script: return loaded_address.hash.hex() if str(get_type) == "p2pkh": return P2pkhScript(loaded_address) elif str(get_type) == "p2sh": return P2shScript(loaded_address)
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 p2sh_p2pkh_script(network: str, address: str) -> P2shScript: '''p2sh embedding p2pkh''' network_params = net_query(network) addr = Address.from_string(network=network_params, string=address) p2pkh = P2pkhScript(addr) return P2shScript(p2pkh)
def script_from_address(address, network="testnet"): if not is_address(address, network): raise AddressError("invalid %s %s address!" % (network, address)) load_address = Address.from_string(address) get_type = load_address.get_type() if str(get_type) == "p2pkh": return P2pkhScript(load_address) elif str(get_type) == "p2sh": return P2shScript(load_address)
def p2sh(self, address=None): """ Get Bitcoin wallet p2sh. :param address: Bitcoin p2sh, default is None. :type address: str :return: str -- Bitcoin p2sh. >>> from shuttle.providers.bitcoin.wallet import Wallet >>> wallet = Wallet(network="testnet") >>> wallet.from_passphrase("meherett") >>> wallet.p2sh() "a914a3c4995d9cd0303e5f89ee1433212c797d04ee5d87" """ if address is None: return P2shScript(P2pkhScript(self._address)).hexlify() if not is_address(address=address, network=self.network): raise AddressError("invalid %s %s address" % (self.network, address)) address = Address.from_string(address) return P2shScript(P2pkhScript(address)).hexlify()
def hash(self): """ Get Bitcoin Hash Time Lock Contract (HTLC) hash. :returns: str -- Bitcoin Hash Time Lock Contract (HTLC) hash. >>> from swap.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc.init("3a26da82ead15a80533a02696656b14b5dbfd84eb14790f2e1be5e9e45820eeb", "muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB", "mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q", 1000) >>> htlc.hash() "a9142bb013c3e4beb08421dedcf815cb65a5c388178b87" """ if self.script is None: raise ValueError("htlc script is none, initialization htlc first") return str(P2shScript(self.script.p2sh_hash()).hexlify())
def hash(self): """ Get bitcoin Hash Time Lock Contract (HTLC) hash. :returns: str -- bitcoin Hash Time Lock Contract (HTLC) hash. >>> from shuttle.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc.init(secret_hash, recipient_address, sender_address, 100) >>> htlc.hash() "a914971894c58d85981c16c2059d422bcde0b156d04487" """ if self.script is None: raise ValueError("htlc script is none, initialization htlc first") return P2shScript(self.script.p2sh_hash()).hexlify()
def address(self): """ Get bitcoin Hash Time Lock Contract (HTLC) address. :returns: str -- bitcoin Hash Time Lock Contract (HTLC) address. >>> from shuttle.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc.init(secret_hash, recipient_address, sender_address, 100) >>> htlc.address() "2N729UBGZB3xjsGFRgKivy4bSjkaJGMVSpB" """ if self.script is None: raise ValueError("htlc script is none, initialization htlc first") return P2shScript(self.script.p2sh_hash()).address(mainnet=self.mainnet)
def address(self): """ Get Bitcoin Hash Time Lock Contract (HTLC) address. :returns: str -- Bitcoin Hash Time Lock Contract (HTLC) address. >>> from swap.providers.bitcoin.htlc import HTLC >>> htlc = HTLC(network="testnet") >>> htlc.init("3a26da82ead15a80533a02696656b14b5dbfd84eb14790f2e1be5e9e45820eeb", "muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB", "mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q", 1000) >>> htlc.address() "2MwEDybGC34949zgzWX4M9FHmE3crDSUydP" """ if self.script is None: raise ValueError("htlc script is none, initialization htlc first") return str( P2shScript(self.script.p2sh_hash()).address(mainnet=self.mainnet))
def hash(self) -> str: """ Get Bitcoin HTLC hash. :returns: str -- Bitcoin Hash Time Lock Contract (HTLC) hash. >>> from swap.providers.bitcoin.htlc import HTLC >>> from swap.utils import sha256 >>> htlc: HTLC = HTLC(network="testnet") >>> htlc.build_htlc(sha256("Hello Meheret!"), "mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", "n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", 1624687630) >>> htlc.hash() "a914c8c77a9b43ee2bdf1a07c48699833d7668bf264c87" """ if self._script is None: raise ValueError("HTLC script is None, first build HTLC.") return str(P2shScript(self._script.p2sh_hash()).hexlify())
def hash(self) -> str: """ Get Bitcoin HTLC hash. :returns: str -- Bitcoin Hash Time Lock Contract (HTLC) hash. >>> from swap.providers.bitcoin.htlc import HTLC >>> from swap.utils import sha256 >>> htlc = HTLC(network="testnet") >>> htlc.build_htlc(sha256("Hello Meheret!"), "mgokpSJoX7npmAK1Zj8ze1926CLxYDt1iF", "mkFWGt4hT11XS8dJKzzRFsTrqjjAwZfQAC", 1000) >>> htlc.hash() "a9149418feed4647e156d6663db3e0cef7c050d0386787" """ if self._script is None: raise ValueError("HTLC script is None, first build HTLC.") return str(P2shScript(self._script.p2sh_hash()).hexlify())
def sign(self, unsigned_raw, solver): """ Sign unsigned refund transaction raw. :param unsigned_raw: bitcoin unsigned refund transaction raw. :type unsigned_raw: str :param solver: bitcoin refund solver. :type solver: bitcoin.solver.RefundSolver :returns: RefundSignature -- bitcoin refund signature instance. >>> from shuttle.providers.bitcoin.signature import RefundSignature >>> refund_signature = RefundSignature() >>> refund_signature.sign(bitcoin_refund_unsigned, refund_solver) <shuttle.providers.bitcoin.signature.RefundSignature 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 \ "recipient_address" not in tx_raw or "sender_address" not in tx_raw or "fee" not in tx_raw: raise ValueError("invalid unsigned refund transaction raw") self.fee = tx_raw["fee"] self.type = tx_raw["type"] if not self.type == "bitcoin_refund_unsigned": raise TypeError("can't sign this %s transaction using RefundSignature" % tx_raw["type"]) if not isinstance(solver, RefundSolver): raise Exception("invalid solver error, only refund solver") htlc = HTLC(network=self.network).init( secret_hash=sha256(solver.secret).hex(), recipient_address=tx_raw["recipient_address"], sender_address=tx_raw["sender_address"], sequence=solver.sequence ) output = TxOut(value=tx_raw["outputs"][0]["amount"], n=tx_raw["outputs"][0]["n"], script_pubkey=P2shScript.unhexlify(tx_raw["outputs"][0]["script"])) self.transaction = MutableTransaction.unhexlify(tx_raw["raw"]) self.transaction.spend([output], [ P2shSolver(htlc.script, solver.solve()) ]) self.signed = b64encode(str(json.dumps(dict( raw=self.transaction.hexlify(), fee=tx_raw["fee"], network=tx_raw["network"], type="bitcoin_refund_signed" ))).encode()).decode() return self
def sign(self, solver): """ Sign Bitcoin claim transaction. :param solver: Bitcoin claim solver. :type solver: bitcoin.solver.ClaimSolver :returns: ClaimTransaction -- Bitcoin claim transaction instance. >>> from swap.providers.bitcoin.transaction import ClaimTransaction >>> from swap.providers.bitcoin.solver import ClaimSolver >>> from swap.providers.bitcoin.wallet import Wallet >>> recipient_wallet = Wallet(network="testnet").from_passphrase("meherett") >>> claim_solver = ClaimSolver(recipient_wallet.private_key(), "Hello Meheret!", "3a26da82ead15a80533a02696656b14b5dbfd84eb14790f2e1be5e9e45820eeb", recipient_wallet.address(), "mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q", 1000) >>> claim_transaction = ClaimTransaction(network="testnet") >>> claim_transaction.build_transaction("1006a6f537fcc4888c65f6ff4f91818a1c6e19bdd3130f59391c00212c552fbd", recipient_wallet, 10000) >>> claim_transaction.sign(solver=claim_solver) <swap.providers.bitcoin.transaction.ClaimTransaction object at 0x0409DAF0> """ # Checking parameter instances if not isinstance(solver, ClaimSolver): raise TypeError("invalid solver instance, only takes Bitcoin ClaimSolver class") if self.transaction is None: raise ValueError("transaction script is none, build transaction first") self.transaction.spend([ TxOut( value=self.htlc_detail["value"], n=0, script_pubkey=P2shScript.unhexlify( hex_string=self.htlc_detail["script"] ) ) ], [ P2shSolver( redeem_script=solver.witness( network=self.network ), redeem_script_solver=solver.solve() ) ]) self._type = "bitcoin_claim_signed" return self
def sign(self, solver): """ Sign Bitcoin refund transaction. :param solver: Bitcoin refund solver. :type solver: bitcoin.solver.RefundSolver :returns: RefundTransaction -- Bitcoin refund transaction instance. >>> from swap.providers.bitcoin.transaction import RefundTransaction >>> from swap.providers.bitcoin.solver import RefundSolver >>> from swap.providers.bitcoin.wallet import Wallet >>> sender_wallet = Wallet(network="testnet").from_passphrase("meherett1234") >>> refund_solver = RefundSolver(sender_wallet.private_key(), "3a26da82ead15a80533a02696656b14b5dbfd84eb14790f2e1be5e9e45820eeb", "muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB", sender_wallet.address(), 1000) >>> refund_transaction = RefundTransaction(network="testnet") >>> refund_transaction.build_transaction("1006a6f537fcc4888c65f6ff4f91818a1c6e19bdd3130f59391c00212c552fbd", sender_wallet, 10000) >>> refund_transaction.sign(solver=refund_solver) <swap.providers.bitcoin.transaction.RefundTransaction object at 0x0409DAF0> """ # Checking parameter instances if not isinstance(solver, RefundSolver): raise TypeError("invalid solver instance, only takes Bitcoin RefundSolver class") if self.transaction is None: raise ValueError("transaction script is none, build transaction first") self.transaction.spend([ TxOut( value=self.htlc_detail["value"], n=0, script_pubkey=P2shScript.unhexlify( hex_string=self.htlc_detail["script"] ) ) ], [ P2shSolver( redeem_script=solver.witness( network=self.network ), redeem_script_solver=solver.solve() ) ]) self._type = "bitcoin_refund_signed" return self
def address(self) -> str: """ Get Bitcoin Hash Time Lock Contract (HTLC) address. :returns: str -- Bitcoin HTLC address. >>> from swap.providers.bitcoin.htlc import HTLC >>> from swap.utils import sha256 >>> htlc = HTLC(network="testnet") >>> htlc.build_htlc(sha256("Hello Meheret!"), "mgokpSJoX7npmAK1Zj8ze1926CLxYDt1iF", "mkFWGt4hT11XS8dJKzzRFsTrqjjAwZfQAC", 1000) >>> htlc.address() "2N6kHwQy6Ph5EdKNgzGrcW2WhGHKGfmP5ae" """ if self._script is None: raise ValueError("HTLC script is None, first build HTLC.") return str( P2shScript(self._script.p2sh_hash()).address( mainnet=(True if self._network == "mainnet" else False)))
def sign(self, solver: WithdrawSolver) -> "WithdrawTransaction": """ Sign Bitcoin withdraw transaction. :param solver: Bitcoin withdraw solver. :type solver: bitcoin.solver.WithdrawSolver :returns: WithdrawTransaction -- Bitcoin withdraw transaction instance. >>> from swap.providers.bitcoin.transaction import WithdrawTransaction >>> from swap.providers.bitcoin.solver import WithdrawSolver >>> withdraw_transaction: WithdrawTransaction = WithdrawTransaction("testnet") >>> withdraw_transaction.build_transaction(address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", transaction_hash="a211d21110756b266925fee2fbf2dc81529beef5e410311b38578dc3a076fb31") >>> bytecode: str = "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a9140a0a6590e6ba4b48118d21b86812615219ece76b88ac67040ec4d660b17576a914e00ff2a640b7ce2d336860739169487a57f84b1588ac68" >>> withdraw_solver: WithdrawSolver = WithdrawSolver(xprivate_key="tprv8ZgxMBicQKsPf949JcuVFLXPJ5m4VKe33gVX3FYVZYVHr2dChU8K66aEQcPdHpUgACq5GQu81Z4e3QN1vxCrV4pxcUcXHoRTamXBRaPdJhW", secret_key="Hello Meheret!", bytecode=bytecode) >>> withdraw_transaction.sign(solver=withdraw_solver) <swap.providers.bitcoin.transaction.WithdrawTransaction object at 0x0409DAF0> """ # Check parameter instances if not isinstance(solver, WithdrawSolver): raise TypeError( f"Solver must be Bitcoin WithdrawSolver, not {type(solver).__name__} type." ) if self._transaction is None: raise ValueError("Transaction is none, build transaction first.") self._transaction.spend([ TxOut(value=self._htlc_utxo["value"], n=0, script_pubkey=P2shScript.unhexlify( hex_string=self._htlc_utxo["script"])) ], [ P2shSolver( redeem_script=solver.witness(network=self._network), redeem_script_solver=solver.solve(network=self._network)) ]) # Set transaction type self._type = "bitcoin_withdraw_signed" return self
def contract_address(self) -> str: """ Get Bitcoin Hash Time Lock Contract (HTLC) address. :returns: str -- Bitcoin HTLC address. >>> from swap.providers.bitcoin.htlc import HTLC >>> from swap.utils import sha256 >>> htlc: HTLC = HTLC(network="testnet") >>> htlc.build_htlc(sha256("Hello Meheret!"), "mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", "n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", 1624687630) >>> htlc.contract_address() "2NBYr6gvh4ujsRwKKjDrrRr2vGonazzX6Z6" """ if self._contract_address: return self._contract_address if self._script is None: raise ValueError("HTLC script is None, first build HTLC.") return str( P2shScript(self._script.p2sh_hash()).address( mainnet=(True if self._network == "mainnet" else False)))
def sign(self, solver: RefundSolver) -> "RefundTransaction": """ Sign Bitcoin refund transaction. :param solver: Bitcoin refund solver. :type solver: bitcoin.solver.RefundSolver :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") >>> bytecode: str = "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a9140a0a6590e6ba4b48118d21b86812615219ece76b88ac67040ec4d660b17576a914e00ff2a640b7ce2d336860739169487a57f84b1588ac68" >>> refund_solver: RefundSolver = RefundSolver(xprivate_key="tprv8ZgxMBicQKsPeMHMJAc6uWGYiGqi1MVM2ybmzXL2TAoDpQe85uyDpdT7mv7Nhdu5rTCBEKLZsd9KyP2LQZJzZTvgVQvENArgU8e6DoYBiXf", bytecode=bytecode, endtime=1624687630) >>> refund_transaction.sign(solver=refund_solver) <swap.providers.bitcoin.transaction.RefundTransaction object at 0x0409DAF0> """ # Check parameter instances if not isinstance(solver, RefundSolver): raise TypeError( f"Solver must be Bitcoin RefundSolver, not {type(solver).__name__} type." ) if self._transaction is None: raise ValueError("Transaction is none, build transaction first.") self._transaction.spend([ TxOut(value=self._htlc_utxo["value"], n=0, script_pubkey=P2shScript.unhexlify( hex_string=self._htlc_utxo["script"])) ], [ P2shSolver( redeem_script=solver.witness(network=self._network), redeem_script_solver=solver.solve(network=self._network)) ]) # Set transaction type self._type = "bitcoin_refund_signed" return self
def script_from_address(address, network="testnet"): """ Get script from address. :param address: Bitcoin address. :type address: str :param network: Bitcoin network, defaults to testnet. :type network: str :returns: P2pkhScript, P2shScript -- Bitcoin p2pkh or p2sh script instance. >>> from shuttle.providers.bitcoin.utils import script_from_address >>> script_from_address("mrmtGq2HMmqAogSsGDjCtXUpxrb7rHThFH", "testnet") P2pkhScript('7b7c4431a43b612a72f8229935c469f1f6903658') """ if not is_address(address, network): raise AddressError("invalid %s %s address!" % (network, address)) load_address = Address.from_string(address) get_type = load_address.get_type() if str(get_type) == "p2pkh": return P2pkhScript(load_address) elif str(get_type) == "p2sh": return P2shScript(load_address)
def sign(self, transaction_raw: str, solver: WithdrawSolver) -> "WithdrawSignature": """ Sign unsigned withdraw transaction raw. :param transaction_raw: Bitcoin unsigned withdraw transaction raw. :type transaction_raw: str :param solver: Bitcoin withdraw solver. :type solver: bitcoin.solver.WithdrawSolver :returns: WithdrawSignature -- Bitcoin withdraw signature instance. >>> from swap.providers.bitcoin.signature import WithdrawSignature >>> from swap.providers.bitcoin.solver import WithdrawSolver >>> unsigned_withdraw_transaction_raw: str = "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTMxZmI3NmEwYzM4ZDU3MzgxYjMxMTBlNGY1ZWU5YjUyODFkY2YyZmJlMmZlMjU2OTI2NmI3NTEwMTFkMjExYTIwMDAwMDAwMDAwZmZmZmZmZmYwMTYwODQwMTAwMDAwMDAwMDAxOTc2YTkxNDBhMGE2NTkwZTZiYTRiNDgxMThkMjFiODY4MTI2MTUyMTllY2U3NmI4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMDAsICJ0eF9vdXRwdXRfbiI6IDAsICJzY3JpcHQiOiAiYTkxNGM4Yzc3YTliNDNlZTJiZGYxYTA3YzQ4Njk5ODMzZDc2NjhiZjI2NGM4NyJ9LCAibmV0d29yayI6ICJ0ZXN0bmV0IiwgInR5cGUiOiAiYml0Y29pbl93aXRoZHJhd191bnNpZ25lZCJ9" >>> bytecode: str = "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a9140a0a6590e6ba4b48118d21b86812615219ece76b88ac67040ec4d660b17576a914e00ff2a640b7ce2d336860739169487a57f84b1588ac68" >>> withdraw_solver: WithdrawSolver = WithdrawSolver(xprivate_key="tprv8ZgxMBicQKsPf949JcuVFLXPJ5m4VKe33gVX3FYVZYVHr2dChU8K66aEQcPdHpUgACq5GQu81Z4e3QN1vxCrV4pxcUcXHoRTamXBRaPdJhW", secret_key="Hello Meheret!", bytecode=bytecode) >>> withdraw_signature: WithdrawSignature = WithdrawSignature(network="testnet") >>> withdraw_signature.sign(transaction_raw=unsigned_withdraw_transaction_raw, solver=withdraw_solver) <swap.providers.bitcoin.signature.WithdrawSignature 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_withdraw_unsigned": raise TypeError(f"Invalid Bitcoin withdraw unsigned transaction raw type, " f"you can't sign {loaded_transaction_raw['type']} type by using withdraw signature.") # Check parameter instances if not isinstance(solver, WithdrawSolver): raise TypeError(f"Solver must be Bitcoin WithdrawSolver, 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"]) ) # Sign withdraw transaction self._transaction.spend([TxOut( value=loaded_transaction_raw["outputs"]["value"], n=loaded_transaction_raw["outputs"]["tx_output_n"], script_pubkey=P2shScript.unhexlify( hex_string=loaded_transaction_raw["outputs"]["script"] ) )], [P2shSolver( redeem_script=solver.witness( network=self._network ), redeem_script_solver=solver.solve( network=self._network ) )]) # Encode withdraw transaction raw self._type = "bitcoin_withdraw_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_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 sign(self, transaction_raw: str, solver: ClaimSolver) -> "ClaimSignature": """ Sign unsigned claim transaction raw. :param transaction_raw: Bitcoin unsigned claim transaction raw. :type transaction_raw: str :param solver: Bitcoin claim solver. :type solver: bitcoin.solver.ClaimSolver :returns: ClaimSignature -- Bitcoin claim signature instance. >>> from swap.providers.bitcoin.signature import ClaimSignature >>> from swap.providers.bitcoin.solver import ClaimSolver >>> unsigned_claim_transaction_raw = "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTE4MjNmMzlhOGM1ZjZmMjc4NDVkZDEzYTY1ZTAzZmUyZWY1MTA4ZDIzNWU3YTM2ZWRiNmViMjY3YjA0NTljNWEwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDBlMjU5ZTA4ZjJlYzlmYzk5YTkyYjZmNjZmZGZjYjNjNzkxNGZkNjg4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMCwgInR4X291dHB1dF9uIjogMCwgInNjcmlwdCI6ICJhOTE0OTQxOGZlZWQ0NjQ3ZTE1NmQ2NjYzZGIzZTBjZWY3YzA1MGQwMzg2Nzg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX2NsYWltX3Vuc2lnbmVkIn0" >>> recipient_root_xprivate_key = "xprv9s21ZrQH143K4Kpce43z5guPyxLrFoc2i8aQAq835Zzp4Rt7i6nZaMCnVSDyHT6MnmJJGKHMrCUqaYpGojrug1ZN5qQDdShQffmkyv5xyUR" >>> bytecode = "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a9140e259e08f2ec9fc99a92b6f66fdfcb3c7914fd6888ac6702e803b27576a91433ecab3d67f0e2bde43e52f41ec1ecbdc73f11f888ac68" >>> claim_solver = ClaimSolver(recipient_root_xprivate_key, "Hello Meheret!", bytecode) >>> claim_signature = ClaimSignature("testnet") >>> claim_signature.sign(transaction_raw=unsigned_claim_transaction_raw, solver=claim_solver) <swap.providers.bitcoin.signature.ClaimSignature 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_claim_unsigned": raise TypeError(f"Invalid Bitcoin claim unsigned transaction raw type, " f"you can't sign {loaded_transaction_raw['type']} type by using claim signature.") # Check parameter instances if not isinstance(solver, ClaimSolver): raise TypeError(f"Solver must be Bitcoin ClaimSolver, 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"]) ) # Sign claim transaction self._transaction.spend([TxOut( value=loaded_transaction_raw["outputs"]["value"], n=loaded_transaction_raw["outputs"]["tx_output_n"], script_pubkey=P2shScript.unhexlify( hex_string=loaded_transaction_raw["outputs"]["script"] ) )], [P2shSolver( redeem_script=solver.witness( network=self._network ), redeem_script_solver=solver.solve( network=self._network ) )]) # Encode claim transaction raw self._type = "bitcoin_claim_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
def sign(self, transaction_raw: str, solver: RefundSolver) -> "RefundSignature": """ Sign unsigned refund transaction raw. :param transaction_raw: Bitcoin unsigned refund transaction raw. :type transaction_raw: str :param solver: Bitcoin refund solver. :type solver: bitcoin.solver.RefundSolver :returns: RefundSignature -- Bitcoin refund signature instance. >>> from swap.providers.bitcoin.signature import Signature >>> from swap.providers.bitcoin.solver import RefundSolver >>> unsigned_refund_transaction_raw: str = "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTMxZmI3NmEwYzM4ZDU3MzgxYjMxMTBlNGY1ZWU5YjUyODFkY2YyZmJlMmZlMjU2OTI2NmI3NTEwMTFkMjExYTIwMDAwMDAwMDAwZmZmZmZmZmYwMTYwODQwMTAwMDAwMDAwMDAxOTc2YTkxNGUwMGZmMmE2NDBiN2NlMmQzMzY4NjA3MzkxNjk0ODdhNTdmODRiMTU4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMDAsICJ0eF9vdXRwdXRfbiI6IDAsICJzY3JpcHQiOiAiYTkxNGM4Yzc3YTliNDNlZTJiZGYxYTA3YzQ4Njk5ODMzZDc2NjhiZjI2NGM4NyJ9LCAibmV0d29yayI6ICJ0ZXN0bmV0IiwgInR5cGUiOiAiYml0Y29pbl9yZWZ1bmRfdW5zaWduZWQifQ" >>> bytecode: str = "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a9140a0a6590e6ba4b48118d21b86812615219ece76b88ac67040ec4d660b17576a914e00ff2a640b7ce2d336860739169487a57f84b1588ac68" >>> refund_solver: RefundSolver = RefundSolver(xprivate_key="tprv8ZgxMBicQKsPeMHMJAc6uWGYiGqi1MVM2ybmzXL2TAoDpQe85uyDpdT7mv7Nhdu5rTCBEKLZsd9KyP2LQZJzZTvgVQvENArgU8e6DoYBiXf", bytecode=bytecode, endtime=1624687630) >>> refund_signature: RefundSignature = RefundSignature(network="testnet") >>> refund_signature.sign(transaction_raw=unsigned_refund_transaction_raw, solver=refund_solver) <swap.providers.bitcoin.signature.RefundSignature 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_refund_unsigned": raise TypeError(f"Invalid Bitcoin refund unsigned transaction raw type, " f"you can't sign {loaded_transaction_raw['type']} type by using refund signature.") # Check parameter instances if not isinstance(solver, RefundSolver): raise TypeError(f"Solver must be Bitcoin RefundSolver, 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"]) ) # Sign refund transaction self._transaction.spend([TxOut( value=loaded_transaction_raw["outputs"]["value"], n=loaded_transaction_raw["outputs"]["tx_output_n"], script_pubkey=P2shScript.unhexlify( hex_string=loaded_transaction_raw["outputs"]["script"] ) )], [P2shSolver( redeem_script=solver.witness( network=self._network ), redeem_script_solver=solver.solve( network=self._network ) )]) # Encode refund transaction raw self._type = "bitcoin_refund_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: RefundSolver) -> "RefundSignature": """ Sign unsigned refund transaction raw. :param transaction_raw: Bitcoin unsigned refund transaction raw. :type transaction_raw: str :param solver: Bitcoin refund solver. :type solver: bitcoin.solver.RefundSolver :returns: RefundSignature -- Bitcoin refund signature instance. >>> from swap.providers.bitcoin.signature import RefundSignature >>> from swap.providers.bitcoin.solver import RefundSolver >>> unsigned_refund_transaction_raw = "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTE4MjNmMzlhOGM1ZjZmMjc4NDVkZDEzYTY1ZTAzZmUyZWY1MTA4ZDIzNWU3YTM2ZWRiNmViMjY3YjA0NTljNWEwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDMzZWNhYjNkNjdmMGUyYmRlNDNlNTJmNDFlYzFlY2JkYzczZjExZjg4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMCwgInR4X291dHB1dF9uIjogMCwgInNjcmlwdCI6ICJhOTE0OTQxOGZlZWQ0NjQ3ZTE1NmQ2NjYzZGIzZTBjZWY3YzA1MGQwMzg2Nzg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX3JlZnVuZF91bnNpZ25lZCJ9" >>> sender_root_xprivate_key = "xprv9s21ZrQH143K3XihXQBN8Uar2WBtrjSzK2oRDEGQ25pA2kKAADoQXaiiVXht163ZTrdtTXfM4GqNRE9gWQHky25BpvBQuuhNCM3SKwWTPNJ" >>> bytecode = "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a9140e259e08f2ec9fc99a92b6f66fdfcb3c7914fd6888ac6702e803b27576a91433ecab3d67f0e2bde43e52f41ec1ecbdc73f11f888ac68" >>> refund_solver = RefundSolver(sender_root_xprivate_key, bytecode, 1000) >>> refund_signature = RefundSignature("testnet") >>> refund_signature.sign(transaction_raw=unsigned_refund_transaction_raw, solver=refund_solver) <swap.providers.bitcoin.signature.RefundSignature 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_refund_unsigned": raise TypeError(f"Invalid Bitcoin refund unsigned transaction raw type, " f"you can't sign {loaded_transaction_raw['type']} type by using refund signature.") # Check parameter instances if not isinstance(solver, RefundSolver): raise TypeError(f"Solver must be Bitcoin RefundSolver, 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"]) ) # Sign refund transaction self._transaction.spend([TxOut( value=loaded_transaction_raw["outputs"]["value"], n=loaded_transaction_raw["outputs"]["tx_output_n"], script_pubkey=P2shScript.unhexlify( hex_string=loaded_transaction_raw["outputs"]["script"] ) )], [P2shSolver( redeem_script=solver.witness( network=self._network ), redeem_script_solver=solver.solve( network=self._network ) )]) # Encode refund transaction raw self._type = "bitcoin_refund_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
def sign(self, unsigned_raw, solver): """ Sign unsigned refund transaction raw. :param unsigned_raw: Bitcoin unsigned refund transaction raw. :type unsigned_raw: str :param solver: Bitcoin refund solver. :type solver: bitcoin.solver.RefundSolver :returns: RefundSignature -- Bitcoin refund signature instance. >>> from shuttle.providers.bitcoin.signature import RefundSignature >>> from shuttle.providers.bitcoin.solver import RefundSolver >>> bitcoin_refund_unsigned_raw = "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTUyYzIzZGM2NDU2N2IxY2ZhZjRkNzc2NjBjNzFjNzUxZjkwZTliYTVjMzc0N2ZhYzFkMDA1MTgwOGVhMGQ2NTEwMDAwMDAwMDAwZmZmZmZmZmYwMTQ4MTEwMDAwMDAwMDAwMDAxOTc2YTkxNDY0YTgzOTBiMGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiA1MDAwLCAibiI6IDAsICJzY3JpcHRfcHVia2V5IjogImE5MTQ0MzNlOGVkNTliOWE2N2YwZjE4N2M2M2ViNDUwYjBkNTZlMjU2ZWMyODcifSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fcmVmdW5kX3Vuc2lnbmVkIn0" >>> refund_solver = RefundSolver("92cbbc5990cb5090326a76feeb321cad01048635afe5756523bbf9f7a75bf38b", "3a26da82ead15a80533a02696656b14b5dbfd84eb14790f2e1be5e9e45820eeb", "muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB", "mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q", 1000) >>> refund_signature = RefundSignature(network="testnet") >>> refund_signature.sign(unsigned_raw=bitcoin_refund_unsigned_raw, solver=refund_solver) <shuttle.providers.bitcoin.signature.RefundSignature object at 0x0409DAF0> """ # Decoding and loading refund transaction refund_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 refund_transaction: raise ValueError( "invalid Bitcoin unsigned refund transaction raw") if not refund_transaction["type"] == "bitcoin_refund_unsigned": raise TypeError( f"invalid Bitcoin refund unsigned transaction type, " f"you can't sign this {refund_transaction['type']} type by using RefundSignature" ) if not isinstance(solver, RefundSolver): raise TypeError( "invalid Bitcoin solver, it's only takes Bitcoin RefundSolver class" ) # Setting transaction fee, type, network and transaction self._fee, self._type, self.network, self.transaction = ( refund_transaction["fee"], refund_transaction["type"], refund_transaction["network"], MutableTransaction.unhexlify(refund_transaction["raw"])) # Signing refund transaction self.transaction.spend([ TxOut( value=refund_transaction["outputs"]["value"], n=refund_transaction["outputs"]["n"], script_pubkey=P2shScript.unhexlify( hex_string=refund_transaction["outputs"]["script_pubkey"])) ], [ P2shSolver(redeem_script=solver.witness( network=refund_transaction["network"]), redeem_script_solver=solver.solve()) ]) # Encoding refund transaction raw self._type = "bitcoin_refund_signed" self._signed_raw = b64encode( str( json.dumps( dict(raw=self.transaction.hexlify(), fee=refund_transaction["fee"], network=refund_transaction["network"], type=self._type))).encode()).decode() return self
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