Пример #1
0
    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
Пример #2
0
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']
Пример #3
0
def decode_transaction_raw(transaction_raw):
    """
    Decode Bitcoin transaction raw.

    :param transaction_raw: Bitcoin transaction raw.
    :type transaction_raw: str
    :returns: dict -- decoded Bitcoin transaction.

    >>> from swap.providers.bitcoin.utils import decode_transaction_raw
    >>> transaction_raw = "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMTg4OGJlN2VjMDY1MDk3ZDk1NjY0NzYzZjI3NmQ0MjU1NTJkNzM1ZmIxZDk3NGFlNzhiZjcyMTA2ZGNhMGYzOTEwMTAwMDAwMDAwZmZmZmZmZmYwMjEwMjcwMDAwMDAwMDAwMDAxN2E5MTQyYmIwMTNjM2U0YmViMDg0MjFkZWRjZjgxNWNiNjVhNWMzODgxNzhiODdiY2RkMGUwMDAwMDAwMDAwMTk3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJhbW91bnQiOiA5ODQ5NDYsICJuIjogMSwgInNjcmlwdCI6ICI3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9"
    >>> decode_transaction_raw(transaction_raw)
    {'fee': 678, 'type': 'bitcoin_fund_unsigned', 'tx': {'hex': '0200000001888be7ec065097d95664763f276d425552d735fb1d974ae78bf72106dca0f3910100000000ffffffff02102700000000000017a9142bb013c3e4beb08421dedcf815cb65a5c388178b87bcdd0e00000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000', 'txid': 'abc70fd3466aec9478ea3115200a84f993204ad1f614fe08e92ecc5997a0d3ba', 'hash': 'abc70fd3466aec9478ea3115200a84f993204ad1f614fe08e92ecc5997a0d3ba', 'size': 117, 'vsize': 117, 'version': 2, 'locktime': 0, 'vin': [{'txid': '91f3a0dc0621f78be74a971dfb35d75255426d273f766456d9975006ece78b88', 'vout': 1, 'scriptSig': {'asm': '', 'hex': ''}, 'sequence': '4294967295'}], 'vout': [{'value': '0.00010000', 'n': 0, 'scriptPubKey': {'asm': 'OP_HASH160 2bb013c3e4beb08421dedcf815cb65a5c388178b OP_EQUAL', 'hex': 'a9142bb013c3e4beb08421dedcf815cb65a5c388178b87', 'type': 'p2sh', 'address': '2MwEDybGC34949zgzWX4M9FHmE3crDSUydP'}}, {'value': '0.00974268', 'n': 1, 'scriptPubKey': {'asm': 'OP_DUP OP_HASH160 64a8390b0b1685fcbf2d4b457118dc8da92d5534 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac', 'type': 'p2pkh', 'address': 'mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q'}}]}, 'network': 'testnet'}
    """

    transaction_raw = str(transaction_raw + "=" * (-len(transaction_raw) % 4))
    try:
        decoded_transaction_raw = json.loads(
            b64decode(str(transaction_raw).encode()).decode())
    except (binascii.Error, json.decoder.JSONDecodeError) as _error:
        raise ValueError("invalid Bitcoin transaction raw")
    if "type" not in decoded_transaction_raw or not str(
            decoded_transaction_raw["type"]).startswith("bitcoin"):
        raise ValueError("invalid Bitcoin transaction raw")
    # Setting testnet
    stp(decoded_transaction_raw["network"], strict=True)
    tx = MutableTransaction.unhexlify(decoded_transaction_raw["raw"])
    return dict(fee=decoded_transaction_raw["fee"],
                type=decoded_transaction_raw["type"],
                tx=tx.to_json(),
                network=decoded_transaction_raw["network"])
Пример #4
0
def decode_transaction_raw(transaction_raw):
    """
    Decode Bitcoin transaction raw.

    :param transaction_raw: Bitcoin transaction raw.
    :type transaction_raw: str
    :returns: dict -- decoded Bitcoin transaction.

    >>> from shuttle.providers.bitcoin.utils import decode_transaction_raw
    >>> decode_transaction_raw(transaction_raw)
    {...}
    """

    transaction_raw = str(transaction_raw + "=" * (-len(transaction_raw) % 4))
    try:
        decoded_transaction_raw = json.loads(b64decode(str(transaction_raw).encode()).decode())
    except (binascii.Error, json.decoder.JSONDecodeError) as _error:
        raise ValueError("invalid Bitcoin transaction raw")
    if "type" not in decoded_transaction_raw or not str(decoded_transaction_raw["type"]).startswith("bitcoin"):
        raise ValueError("invalid Bitcoin transaction raw")
    # Setting testnet
    stp(decoded_transaction_raw["network"], strict=True)
    tx = MutableTransaction.unhexlify(decoded_transaction_raw["raw"])
    return dict(
        fee=decoded_transaction_raw["fee"],
        type=decoded_transaction_raw["type"],
        tx=tx.to_json(),
        network=decoded_transaction_raw["network"]
    )
Пример #5
0
def make_raw_transaction(inputs: list,
                         outputs: list,
                         locktime=Locktime(0),
                         timestamp: int = int(time()),
                         version=1):
    '''create raw transaction'''

    return MutableTransaction(version, timestamp, inputs, outputs, locktime)
Пример #6
0
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)
Пример #7
0
    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
Пример #8
0
def decode_transaction_raw(transaction_raw: str,
                           offline: bool = True,
                           headers: dict = config["headers"],
                           timeout: int = config["timeout"]) -> dict:
    """
    Decode Bitcoin transaction raw.

    :param transaction_raw: Bitcoin transaction raw.
    :type transaction_raw: str
    :param offline: Offline decode, defaults to True.
    :type offline: bool
    :param headers: Request headers, default to common headers.
    :type headers: dict
    :param timeout: Request timeout, default to 60.
    :type timeout: int

    :returns: dict -- Decoded Bitcoin transaction raw.

    >>> from swap.providers.bitcoin.utils import decode_transaction_raw
    >>> transaction_raw = "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMTg4OGJlN2VjMDY1MDk3ZDk1NjY0NzYzZjI3NmQ0MjU1NTJkNzM1ZmIxZDk3NGFlNzhiZjcyMTA2ZGNhMGYzOTEwMTAwMDAwMDAwZmZmZmZmZmYwMjEwMjcwMDAwMDAwMDAwMDAxN2E5MTQyYmIwMTNjM2U0YmViMDg0MjFkZWRjZjgxNWNiNjVhNWMzODgxNzhiODdiY2RkMGUwMDAwMDAwMDAwMTk3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJhbW91bnQiOiA5ODQ5NDYsICJuIjogMSwgInNjcmlwdCI6ICI3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9"
    >>> decode_transaction_raw(transaction_raw=transaction_raw)
    {'fee': 678, 'type': 'bitcoin_fund_unsigned', 'tx': {'hex': '0200000001888be7ec065097d95664763f276d425552d735fb1d974ae78bf72106dca0f3910100000000ffffffff02102700000000000017a9142bb013c3e4beb08421dedcf815cb65a5c388178b87bcdd0e00000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000', 'txid': 'abc70fd3466aec9478ea3115200a84f993204ad1f614fe08e92ecc5997a0d3ba', 'hash': 'abc70fd3466aec9478ea3115200a84f993204ad1f614fe08e92ecc5997a0d3ba', 'size': 117, 'vsize': 117, 'version': 2, 'locktime': 0, 'vin': [{'txid': '91f3a0dc0621f78be74a971dfb35d75255426d273f766456d9975006ece78b88', 'vout': 1, 'scriptSig': {'asm': '', 'hex': ''}, 'sequence': '4294967295'}], 'vout': [{'value': '0.00010000', 'n': 0, 'scriptPubKey': {'asm': 'OP_HASH160 2bb013c3e4beb08421dedcf815cb65a5c388178b OP_EQUAL', 'hex': 'a9142bb013c3e4beb08421dedcf815cb65a5c388178b87', 'type': 'p2sh', 'address': '2MwEDybGC34949zgzWX4M9FHmE3crDSUydP'}}, {'value': '0.00974268', 'n': 1, 'scriptPubKey': {'asm': 'OP_DUP OP_HASH160 64a8390b0b1685fcbf2d4b457118dc8da92d5534 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac', 'type': 'p2pkh', 'address': 'mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q'}}]}, 'network': 'testnet'}
    """

    if not is_transaction_raw(transaction_raw=transaction_raw):
        raise TransactionRawError("Invalid Bitcoin 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())

    decoded_transaction: Optional[dict] = None

    if offline:
        stp(loaded_transaction_raw["network"], strict=True, force=True)
        tx = MutableTransaction.unhexlify(loaded_transaction_raw["raw"])
        decoded_transaction = tx.to_json()
    else:
        url = f"{config[loaded_transaction_raw['network']]['blockcypher']['url']}/txs/decode"
        parameter = dict(token=config[loaded_transaction_raw["network"]]
                         ["blockcypher"]["token"])
        data = dict(tx=loaded_transaction_raw["raw"])
        response = requests.post(url=url,
                                 data=json.dumps(data),
                                 params=parameter,
                                 headers=headers,
                                 timeout=timeout)
        decoded_transaction = response.json()

    return dict(fee=loaded_transaction_raw["fee"],
                type=loaded_transaction_raw["type"],
                tx=decoded_transaction,
                network=loaded_transaction_raw["network"])
Пример #9
0
    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
Пример #10
0
def decode_raw(raw: str, network: str = config["network"], offline: bool = True,
               headers: dict = config["headers"], timeout: int = config["timeout"]) -> dict:
    """
    Decode original Bitcoin raw.

    :param raw: Bitcoin transaction raw.
    :type raw: str
    :param network: Bitcoin network, defaults to mainnet.
    :type network: str
    :param offline: Offline decode, defaults to True.
    :type offline: bool
    :param headers: Request headers, default to common headers.
    :type headers: dict
    :param timeout: Request timeout, default to 60.
    :type timeout: int

    :returns: dict -- Bitcoin decoded transaction raw.

    >>> from swap.providers.bitcoin.rpc import decode_raw
    >>> decode_raw(raw="02000000011823f39a8c5f6f27845dd13a65e03fe2ef5108d235e7a36edb6eb267b0459c5a010000006a47304402207018b7fd1ba6624fe9bb0f16cd65fa243d202e32fdff452699f56465b61ab648022009f0dc1a0a63109246c45e120fc0d34b40e789dfc4d05e64f269602c7d67d9210121027f0dc0894bd690635412af782d05e4f79d3d40bf568978c650f3f1ca1a96cf36ffffffff02102700000000000017a9149418feed4647e156d6663db3e0cef7c050d038678734330100000000001976a91433ecab3d67f0e2bde43e52f41ec1ecbdc73f11f888ac00000000", network="testnet")
    {'hex': '02000000011823f39a8c5f6f27845dd13a65e03fe2ef5108d235e7a36edb6eb267b0459c5a010000006a47304402207018b7fd1ba6624fe9bb0f16cd65fa243d202e32fdff452699f56465b61ab648022009f0dc1a0a63109246c45e120fc0d34b40e789dfc4d05e64f269602c7d67d9210121027f0dc0894bd690635412af782d05e4f79d3d40bf568978c650f3f1ca1a96cf36ffffffff02102700000000000017a9149418feed4647e156d6663db3e0cef7c050d038678734330100000000001976a91433ecab3d67f0e2bde43e52f41ec1ecbdc73f11f888ac00000000', 'txid': '6e5c80f600f45acda3c3101128bb3075bf2cf7af4bab0d99c9d856ebfb4b0953', 'hash': '6e5c80f600f45acda3c3101128bb3075bf2cf7af4bab0d99c9d856ebfb4b0953', 'size': 223, 'vsize': 223, 'version': 2, 'locktime': 0, 'vin': [{'txid': '5a9c45b067b26edb6ea3e735d20851efe23fe0653ad15d84276f5f8c9af32318', 'vout': 1, 'scriptSig': {'asm': '304402207018b7fd1ba6624fe9bb0f16cd65fa243d202e32fdff452699f56465b61ab648022009f0dc1a0a63109246c45e120fc0d34b40e789dfc4d05e64f269602c7d67d92101 027f0dc0894bd690635412af782d05e4f79d3d40bf568978c650f3f1ca1a96cf36', 'hex': '47304402207018b7fd1ba6624fe9bb0f16cd65fa243d202e32fdff452699f56465b61ab648022009f0dc1a0a63109246c45e120fc0d34b40e789dfc4d05e64f269602c7d67d9210121027f0dc0894bd690635412af782d05e4f79d3d40bf568978c650f3f1ca1a96cf36'}, 'sequence': '4294967295'}], 'vout': [{'value': '0.00010000', 'n': 0, 'scriptPubKey': {'asm': 'OP_HASH160 9418feed4647e156d6663db3e0cef7c050d03867 OP_EQUAL', 'hex': 'a9149418feed4647e156d6663db3e0cef7c050d0386787', 'type': 'p2sh', 'address': '2N6kHwQy6Ph5EdKNgzGrcW2WhGHKGfmP5ae'}}, {'value': '0.00078644', 'n': 1, 'scriptPubKey': {'asm': 'OP_DUP OP_HASH160 33ecab3d67f0e2bde43e52f41ec1ecbdc73f11f8 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91433ecab3d67f0e2bde43e52f41ec1ecbdc73f11f888ac', 'type': 'p2pkh', 'address': 'mkFWGt4hT11XS8dJKzzRFsTrqjjAwZfQAC'}}]}
    """

    if not is_network(network=network):
        raise NetworkError(f"Invalid Bitcoin '{network}' network",
                           "choose only 'mainnet' or 'testnet' networks.")

    if offline:
        stp(network, strict=True, force=True)
        tx = MutableTransaction.unhexlify(raw)
        return tx.to_json()

    url = f"{config[network]['blockcypher']['url']}/txs/decode"
    parameter = dict(token=config[network]["blockcypher"]["token"])
    data = dict(tx=raw)
    response = requests.post(
        url=url, data=json.dumps(data), params=parameter, headers=headers, timeout=timeout
    )
    response_json = response.json()
    return response_json
Пример #11
0
    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
Пример #12
0
    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
Пример #13
0
    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
Пример #14
0
    def build_transaction(
            self,
            address: str,
            recipients: dict,
            unit: str = config["unit"],
            locktime: int = config["locktime"]) -> "NormalTransaction":
        """
        Build Bitcoin normal transaction.

        :param address: Bitcoin sender address.
        :type address: str
        :param recipients: Recipients Bitcoin address and amount.
        :type recipients: dict
        :param unit: Bitcoin unit, default to ``Satoshi``.
        :type unit: str
        :param locktime: Bitcoin transaction lock time, defaults to ``0``.
        :type locktime: int

        :returns: NormalTransaction -- Bitcoin normal transaction instance.

        >>> from swap.providers.bitcoin.transaction import NormalTransaction
        >>> normal_transaction: NormalTransaction = NormalTransaction(network="testnet")
        >>> normal_transaction.build_transaction(address="mkFWGt4hT11XS8dJKzzRFsTrqjjAwZfQAC", recipients={"2N6kHwQy6Ph5EdKNgzGrcW2WhGHKGfmP5ae": 10000000}, locktime=0)
        <swap.providers.bitcoin.transaction.NormalTransaction object at 0x0409DAF0>
        """

        # Check parameter instances
        if not is_address(address, self._network):
            raise AddressError(
                f"Invalid Bitcoin sender '{address}' {self._network} address.")
        if unit not in ["BTC", "mBTC", "Satoshi"]:
            raise UnitError(
                "Invalid Bitcoin unit, choose only 'BTC', 'mBTC' or 'Satoshi' units."
            )

        self._address, outputs, self._amount = (address, [], (
            sum(recipients.values())
            if unit == "Satoshi" else amount_unit_converter(
                amount=sum(recipients.values()), unit_from=f"{unit}2Satoshi")))
        # Get Sender UTXO's
        self._utxos = get_utxos(address=self._address, network=self._network)
        # Outputs action
        for _address, _amount in recipients.items():
            if not is_address(_address, self._network):
                raise AddressError(
                    f"Invalid Bitcoin recipients '{_address}' {self._network} address."
                )
            outputs.append(
                TxOut(value=int(_amount),
                      n=len(outputs),
                      script_pubkey=get_address_hash(address=_address,
                                                     script=True)))
        # Get previous transaction indexes
        self._previous_transaction_indexes, max_amount = _get_previous_transaction_indexes(
            utxos=self._utxos,
            amount=self._amount,
            transaction_output=len(outputs))
        # Build transaction inputs
        inputs, amount = _build_inputs(
            utxos=self._utxos,
            previous_transaction_indexes=self._previous_transaction_indexes)
        # Calculate the fee
        self._fee = fee_calculator(len(inputs), len(outputs))

        if amount < self._amount:
            raise BalanceError("Insufficient spend UTXO's",
                               "you don't have enough amount.")
        elif amount < (self._amount + self._fee):
            raise BalanceError(
                f"You don't have enough amount to pay '{self._fee}' Satoshi fee",
                f"you can spend maximum '{amount - self._fee}' Satoshi amount."
            )

        return_amount: int = int(amount - (self._amount + self._fee))
        if return_amount != 0:
            outputs.append(
                TxOut(value=return_amount,
                      n=len(outputs),
                      script_pubkey=get_address_hash(address=self._address,
                                                     script=True)))

        # Build mutable transaction
        self._transaction = MutableTransaction(version=self._version,
                                               ins=inputs,
                                               outs=outputs,
                                               locktime=Locktime(locktime))
        # Set transaction type
        self._type = "bitcoin_normal_unsigned"
        return self
Пример #15
0
    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
Пример #16
0
    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()
Пример #17
0
# 设置罚金
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])
print('commit_tx_hex: ', signed.hexlify())

# 发送交易
Пример #18
0
    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
Пример #19
0
class FundTransaction(Transaction):
    """
    Bitcoin Fund transaction.

    :param network: Bitcoin network, defaults to ``mainnet``.
    :type network: str
    :param version: Bitcoin transaction version, defaults to ``2``.
    :type version: int

    :returns: FundTransaction -- Bitcoin fund transaction instance.

    .. warning::
        Do not forget to build transaction after initialize fund transaction.
    """
    def __init__(self,
                 network: str = config["network"],
                 version: int = config["version"]):
        super().__init__(network=network, version=version)

        self._htlc: Optional[HTLC] = None
        self._utxos: Optional[list] = None
        self._previous_transaction_indexes: Optional[list] = None
        self._interest: Optional[int] = None

    def build_transaction(
            self,
            address: str,
            htlc: HTLC,
            amount: Optional[Union[int, float]],
            unit: str = config["unit"],
            locktime: int = config["locktime"]) -> "FundTransaction":
        """
        Build Bitcoin fund transaction.

        :param address: Bitcoin sender address.
        :type address: str
        :param htlc: Bitcoin HTLC instance.
        :type htlc: bitcoin.htlc.HTLC
        :param amount: Bitcoin amount, default to ``None``.
        :type amount: int, float
        :param unit: Bitcoin unit, default to ``Satoshi``.
        :type unit: str
        :param locktime: Bitcoin transaction lock time, defaults to ``0``.
        :type locktime: int

        :returns: FundTransaction -- Bitcoin fund transaction instance.

        >>> from swap.providers.bitcoin.htlc import HTLC
        >>> from swap.providers.bitcoin.transaction import FundTransaction
        >>> from swap.utils import sha256
        >>> htlc: HTLC = HTLC(network="testnet")
        >>> htlc.build_htlc(secret_hash=sha256("Hello Meheret!"), recipient_address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", sender_address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", endtime=1624687630)
        >>> fund_transaction: FundTransaction = FundTransaction(network="testnet")
        >>> fund_transaction.build_transaction(address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", htlc=htlc, amount=0.001, unit="BTC")
        <swap.providers.bitcoin.transaction.FundTransaction object at 0x0409DAF0>
        """

        # Check parameter instances
        if not is_address(address, self._network):
            raise AddressError(
                f"Invalid Bitcoin sender '{address}' {self._network} address.")
        if not isinstance(htlc, HTLC):
            raise TypeError(
                "Invalid Bitcoin HTLC instance, only takes xinfin HTLC class")
        if htlc.agreements and address != htlc.agreements["sender_address"]:
            raise AddressError(
                f"Wrong Bitcoin sender '{address}' address",
                "address must be equal with HTLC agreements sender address.")
        if unit not in ["BTC", "mBTC", "Satoshi"]:
            raise UnitError(
                "Invalid Bitcoin unit, choose only 'BTC', 'mBTC' or 'Satoshi' units."
            )

        self._address, outputs, self._htlc, self._amount = (
            address, [], htlc,
            (amount if unit == "Satoshi" else amount_unit_converter(
                amount=amount, unit_from=f"{unit}2Satoshi")))

        # Get Sender UTXO's
        self._utxos = get_utxos(address=self._address, network=self._network)
        # Outputs action
        outputs.append(
            TxOut(value=self._amount,
                  n=0,
                  script_pubkey=get_address_hash(
                      address=self._htlc.contract_address(), script=True)))
        # Get previous transaction indexes
        self._previous_transaction_indexes, max_amount = _get_previous_transaction_indexes(
            utxos=self._utxos, amount=self._amount, transaction_output=2)
        # Build transaction inputs
        inputs, amount = _build_inputs(
            utxos=self._utxos,
            previous_transaction_indexes=self._previous_transaction_indexes)
        # Calculate the fee
        self._fee = fee_calculator(len(inputs), 2)

        if amount < self._amount:
            raise BalanceError("Insufficient spend UTXO's",
                               "you don't have enough amount.")
        elif amount < (self._amount + self._fee):
            raise BalanceError(
                f"You don't have enough amount to pay '{self._fee}' Satoshi fee",
                f"you can spend maximum '{amount - self._fee}' Satoshi amount."
            )

        return_amount: int = int(amount - (self._amount + self._fee))
        if return_amount != 0:
            outputs.append(
                TxOut(value=return_amount,
                      n=len(outputs),
                      script_pubkey=get_address_hash(address=self._address,
                                                     script=True)))

        # Build mutable transaction
        self._transaction = MutableTransaction(version=self._version,
                                               ins=inputs,
                                               outs=outputs,
                                               locktime=Locktime(locktime))
        # Set transaction type
        self._type = "bitcoin_fund_unsigned"
        return self

    def sign(self, solver: FundSolver) -> "FundTransaction":
        """
        Sign Bitcoin fund transaction.

        :param solver: Bitcoin fund solver.
        :type solver: bitcoin.solver.FundSolver

        :returns: FundTransaction -- Bitcoin fund transaction instance.

        >>> from swap.providers.bitcoin.htlc import HTLC
        >>> from swap.providers.bitcoin.transaction import FundTransaction
        >>> from swap.providers.bitcoin.solver import FundSolver
        >>> from swap.utils import sha256
        >>> htlc: HTLC = HTLC(network="testnet")
        >>> htlc.build_htlc(secret_hash=sha256("Hello Meheret!"), recipient_address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", sender_address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", endtime=1624687630)
        >>> fund_transaction: FundTransaction = FundTransaction(network="testnet")
        >>> fund_transaction.build_transaction(address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", htlc=htlc, amount=0.001, unit="BTC")
        >>> fund_solver: FundSolver = FundSolver(xprivate_key="tprv8ZgxMBicQKsPeMHMJAc6uWGYiGqi1MVM2ybmzXL2TAoDpQe85uyDpdT7mv7Nhdu5rTCBEKLZsd9KyP2LQZJzZTvgVQvENArgU8e6DoYBiXf")
        >>> fund_transaction.sign(solver=fund_solver)
        <swap.providers.bitcoin.transaction.FundTransaction object at 0x0409DAF0>
        """

        # Check parameter instances
        if not isinstance(solver, FundSolver):
            raise TypeError(
                f"Solver must be Bitcoin FundSolver, not {type(solver).__name__} type."
            )
        if self._transaction is None:
            raise ValueError("Transaction is none, build transaction first.")

        # Organize outputs
        outputs = _build_outputs(
            utxos=self._utxos,
            previous_transaction_indexes=self._previous_transaction_indexes)
        # Sign fund transaction
        self._transaction.spend(
            txouts=outputs,
            solvers=[solver.solve(network=self._network) for _ in outputs])
        # Set transaction type
        self._type = "bitcoin_fund_signed"
        return self

    def transaction_raw(self) -> str:
        """
        Get Bitcoin fund transaction raw.

        :returns: str -- Bitcoin fund transaction raw.

        >>> from swap.providers.bitcoin.htlc import HTLC
        >>> from swap.providers.bitcoin.transaction import FundTransaction
        >>> from swap.utils import sha256
        >>> htlc: HTLC = HTLC(network="testnet")
        >>> htlc.build_htlc(secret_hash=sha256("Hello Meheret!"), recipient_address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", sender_address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", endtime=1624687630)
        >>> fund_transaction: FundTransaction = FundTransaction(network="testnet")
        >>> fund_transaction.build_transaction(address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", htlc=htlc, amount=0.001, unit="BTC")
        >>> fund_transaction.transaction_raw()
        "eyJmZWUiOiAxMTIyLCAicmF3IjogIjAyMDAwMDAwMDIzMWZiNzZhMGMzOGQ1NzM4MWIzMTEwZTRmNWVlOWI1MjgxZGNmMmZiZTJmZTI1NjkyNjZiNzUxMDExZDIxMWEyMDEwMDAwMDAwMGZmZmZmZmZmMDgwYjgyZWVjMzMyOTk2YTQyMmFlNGYwODBmNzRlNTNmZDJjYTRmMDcwMTFkNDdjNTkwODUzZTFlMzA1ZmUxMTAxMDAwMDAwMDBmZmZmZmZmZjAyYTA4NjAxMDAwMDAwMDAwMDE3YTkxNGM4Yzc3YTliNDNlZTJiZGYxYTA3YzQ4Njk5ODMzZDc2NjhiZjI2NGM4NzMyOWMwZDAwMDAwMDAwMDAxOTc2YTkxNGUwMGZmMmE2NDBiN2NlMmQzMzY4NjA3MzkxNjk0ODdhNTdmODRiMTU4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IFt7InZhbHVlIjogOTQzMzAsICJ0eF9vdXRwdXRfbiI6IDEsICJzY3JpcHQiOiAiNzZhOTE0ZTAwZmYyYTY0MGI3Y2UyZDMzNjg2MDczOTE2OTQ4N2E1N2Y4NGIxNTg4YWMifSwgeyJ2YWx1ZSI6IDg5ODc0NiwgInR4X291dHB1dF9uIjogMSwgInNjcmlwdCI6ICI3NmE5MTRlMDBmZjJhNjQwYjdjZTJkMzM2ODYwNzM5MTY5NDg3YTU3Zjg0YjE1ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9"
        """

        # Check parameter instances
        if self._transaction is None:
            raise ValueError("Transaction is none, build transaction first.")

        # Encode fund transaction raw
        if self._type == "bitcoin_fund_signed":
            return clean_transaction_raw(
                b64encode(
                    str(
                        json.dumps(
                            dict(
                                raw=self._transaction.hexlify(),
                                fee=self._fee,
                                network=self._network,
                                type=self._type,
                            ))).encode()).decode())
        return clean_transaction_raw(
            b64encode(
                str(
                    json.dumps(
                        dict(
                            fee=self._fee,
                            raw=self._transaction.hexlify(),
                            outputs=_build_outputs(
                                utxos=self._utxos,
                                previous_transaction_indexes=self.
                                _previous_transaction_indexes,
                                only_dict=True),
                            network=self._network,
                            type=self._type,
                        ))).encode()).decode())
Пример #20
0
    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
Пример #21
0
class RefundTransaction(Transaction):
    """
    Bitcoin RefundTransaction class.

    :param version: Bitcoin transaction version, defaults to 2.
    :type version: int
    :param network: Bitcoin network, defaults to testnet.
    :type network: str
    :returns:  Transaction -- Bitcoin transaction instance.

    .. warning::
        Do not forget to build transaction after initialize refund transaction.
    """

    # Initialization claim transaction
    def __init__(self, version=2, network="testnet"):
        super().__init__(version=version, network=network)
        # Initialization transaction_id, sender wallet and amount
        self.transaction_id, self.wallet, \
            self.amount = None, None, None
        # Get transaction detail
        self.transaction_detail = None
        # Transaction detail outputs (HTLC and Sender account)
        self.htlc_detail, self.sender_detail = None, None

    # Building transaction
    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

    # Signing refund transaction
    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
        >>> from shuttle.providers.bitcoin.solver import RefundSolver
        >>> from shuttle.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)
        <shuttle.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 unsigned_raw(self):
        """
        Get Bitcoin unsigned refund transaction raw.

        :returns: str -- Bitcoin unsigned refund transaction raw.

        >>> from shuttle.providers.bitcoin.transaction import RefundTransaction
        >>> from shuttle.providers.bitcoin.wallet import Wallet
        >>> sender_wallet = Wallet(network="testnet").from_passphrase("meherett1234")
        >>> refund_transaction = RefundTransaction(network="testnet")
        >>> refund_transaction.build_transaction("1006a6f537fcc4888c65f6ff4f91818a1c6e19bdd3130f59391c00212c552fbd", sender_wallet, 10000)
        >>> refund_transaction.unsigned_raw()
        "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMTJjMzkyMjE3NDgzOTA2ZjkwMmU3M2M0YmMxMzI4NjRkZTU4MTUzNzcyZDc5MjY4OTYwOTk4MTYyMjY2NjM0YmUwMTAwMDAwMDAwZmZmZmZmZmYwMmU4MDMwMDAwMDAwMDAwMDAxN2E5MTQ5NzE4OTRjNThkODU5ODFjMTZjMjA1OWQ0MjJiY2RlMGIxNTZkMDQ0ODdhNjI5MDAwMDAwMDAwMDAwMTk3NmE5MTQ2YmNlNjVlNThhNTBiOTc5ODk5MzBlOWE0ZmYxYWMxYTc3NTE1ZWYxODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJhbW91bnQiOiAxMjM0MCwgIm4iOiAxLCAic2NyaXB0IjogIjc2YTkxNDZiY2U2NWU1OGE1MGI5Nzk4OTkzMGU5YTRmZjFhYzFhNzc1MTVlZjE4OGFjIn1dLCAidHlwZSI6ICJiaXRjb2luX2Z1bmRfdW5zaWduZWQifQ"
        """

        # Checking transaction
        if self.transaction is None:
            raise ValueError(
                "transaction script is none, build transaction first")

        # Encoding refund transaction raw
        return b64encode(
            str(
                json.dumps(
                    dict(fee=self._fee,
                         raw=self.transaction.hexlify(),
                         outputs=dict(
                             value=self.htlc_detail["value"],
                             n=0,
                             script_pubkey=self.htlc_detail["script"]),
                         network=self.network,
                         type="bitcoin_refund_unsigned"))).encode()).decode()
Пример #22
0
    def build_transaction(
            self,
            address: str,
            htlc: HTLC,
            amount: Optional[Union[int, float]],
            unit: str = config["unit"],
            locktime: int = config["locktime"]) -> "FundTransaction":
        """
        Build Bitcoin fund transaction.

        :param address: Bitcoin sender address.
        :type address: str
        :param htlc: Bitcoin HTLC instance.
        :type htlc: bitcoin.htlc.HTLC
        :param amount: Bitcoin amount, default to ``None``.
        :type amount: int, float
        :param unit: Bitcoin unit, default to ``Satoshi``.
        :type unit: str
        :param locktime: Bitcoin transaction lock time, defaults to ``0``.
        :type locktime: int

        :returns: FundTransaction -- Bitcoin fund transaction instance.

        >>> from swap.providers.bitcoin.htlc import HTLC
        >>> from swap.providers.bitcoin.transaction import FundTransaction
        >>> from swap.utils import sha256
        >>> htlc: HTLC = HTLC(network="testnet")
        >>> htlc.build_htlc(secret_hash=sha256("Hello Meheret!"), recipient_address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", sender_address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", endtime=1624687630)
        >>> fund_transaction: FundTransaction = FundTransaction(network="testnet")
        >>> fund_transaction.build_transaction(address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", htlc=htlc, amount=0.001, unit="BTC")
        <swap.providers.bitcoin.transaction.FundTransaction object at 0x0409DAF0>
        """

        # Check parameter instances
        if not is_address(address, self._network):
            raise AddressError(
                f"Invalid Bitcoin sender '{address}' {self._network} address.")
        if not isinstance(htlc, HTLC):
            raise TypeError(
                "Invalid Bitcoin HTLC instance, only takes xinfin HTLC class")
        if htlc.agreements and address != htlc.agreements["sender_address"]:
            raise AddressError(
                f"Wrong Bitcoin sender '{address}' address",
                "address must be equal with HTLC agreements sender address.")
        if unit not in ["BTC", "mBTC", "Satoshi"]:
            raise UnitError(
                "Invalid Bitcoin unit, choose only 'BTC', 'mBTC' or 'Satoshi' units."
            )

        self._address, outputs, self._htlc, self._amount = (
            address, [], htlc,
            (amount if unit == "Satoshi" else amount_unit_converter(
                amount=amount, unit_from=f"{unit}2Satoshi")))

        # Get Sender UTXO's
        self._utxos = get_utxos(address=self._address, network=self._network)
        # Outputs action
        outputs.append(
            TxOut(value=self._amount,
                  n=0,
                  script_pubkey=get_address_hash(
                      address=self._htlc.contract_address(), script=True)))
        # Get previous transaction indexes
        self._previous_transaction_indexes, max_amount = _get_previous_transaction_indexes(
            utxos=self._utxos, amount=self._amount, transaction_output=2)
        # Build transaction inputs
        inputs, amount = _build_inputs(
            utxos=self._utxos,
            previous_transaction_indexes=self._previous_transaction_indexes)
        # Calculate the fee
        self._fee = fee_calculator(len(inputs), 2)

        if amount < self._amount:
            raise BalanceError("Insufficient spend UTXO's",
                               "you don't have enough amount.")
        elif amount < (self._amount + self._fee):
            raise BalanceError(
                f"You don't have enough amount to pay '{self._fee}' Satoshi fee",
                f"you can spend maximum '{amount - self._fee}' Satoshi amount."
            )

        return_amount: int = int(amount - (self._amount + self._fee))
        if return_amount != 0:
            outputs.append(
                TxOut(value=return_amount,
                      n=len(outputs),
                      script_pubkey=get_address_hash(address=self._address,
                                                     script=True)))

        # Build mutable transaction
        self._transaction = MutableTransaction(version=self._version,
                                               ins=inputs,
                                               outs=outputs,
                                               locktime=Locktime(locktime))
        # Set transaction type
        self._type = "bitcoin_fund_unsigned"
        return self
Пример #23
0
class WithdrawTransaction(Transaction):
    """
    Bitcoin Withdraw transaction.

    :param network: Bitcoin network, defaults to ``mainnet``.
    :type network: str
    :param version: Bitcoin transaction version, defaults to ``2``.
    :type version: int

    :returns: WithdrawTransaction -- Bitcoin withdraw transaction instance.

    .. warning::
        Do not forget to build transaction after initialize withdraw transaction.
    """
    def __init__(self,
                 network: str = config["network"],
                 version: int = config["version"]):
        super().__init__(network=network, version=version)

        self._transaction_hash: Optional[str] = None
        self._transaction_detail: Optional[dict] = None
        self._htlc_utxo: Optional[dict] = None
        self._interest: Optional[int] = None

    def build_transaction(
            self,
            address: str,
            transaction_hash: str,
            locktime: int = config["locktime"]) -> "WithdrawTransaction":
        """
        Build Bitcoin withdraw transaction.

        :param address: Bitcoin recipient 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: WithdrawTransaction -- Bitcoin withdraw transaction instance.

        >>> from swap.providers.bitcoin.transaction import WithdrawTransaction
        >>> withdraw_transaction: WithdrawTransaction = WithdrawTransaction("testnet")
        >>> withdraw_transaction.build_transaction(address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", transaction_hash="a211d21110756b266925fee2fbf2dc81529beef5e410311b38578dc3a076fb31")
        <swap.providers.bitcoin.transaction.WithdrawTransaction object at 0x0409DAF0>
        """

        # Check parameter instances
        if not is_address(address, self._network):
            raise AddressError(
                f"Invalid Bitcoin recipient '{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 hash, 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_withdraw_unsigned"
        return self

    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 transaction_raw(self) -> str:
        """
        Get Bitcoin withdraw transaction raw.

        :returns: str -- Bitcoin withdraw transaction raw.

        >>> from swap.providers.bitcoin.transaction import WithdrawTransaction
        >>> withdraw_transaction: WithdrawTransaction = WithdrawTransaction("testnet")
        >>> withdraw_transaction.build_transaction(address="mgS3WMHp9nvdUPeDJxr5iCF2P5HuFZSR3V", transaction_hash="a211d21110756b266925fee2fbf2dc81529beef5e410311b38578dc3a076fb31")
        >>> withdraw_transaction.transaction_raw()
        "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTMxZmI3NmEwYzM4ZDU3MzgxYjMxMTBlNGY1ZWU5YjUyODFkY2YyZmJlMmZlMjU2OTI2NmI3NTEwMTFkMjExYTIwMDAwMDAwMDAwZmZmZmZmZmYwMTYwODQwMTAwMDAwMDAwMDAxOTc2YTkxNDBhMGE2NTkwZTZiYTRiNDgxMThkMjFiODY4MTI2MTUyMTllY2U3NmI4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMDAsICJ0eF9vdXRwdXRfbiI6IDAsICJzY3JpcHQiOiAiYTkxNGM4Yzc3YTliNDNlZTJiZGYxYTA3YzQ4Njk5ODMzZDc2NjhiZjI2NGM4NyJ9LCAibmV0d29yayI6ICJ0ZXN0bmV0IiwgInR5cGUiOiAiYml0Y29pbl93aXRoZHJhd191bnNpZ25lZCJ9"
        """

        # Check transaction
        if self._transaction is None:
            raise ValueError("Transaction is none, build transaction first.")

        # Encode withdraw transaction raw
        if self._type == "bitcoin_withdraw_signed":
            return clean_transaction_raw(
                b64encode(
                    str(
                        json.dumps(
                            dict(
                                raw=self._transaction.hexlify(),
                                fee=self._fee,
                                network=self._network,
                                type=self._type,
                            ))).encode()).decode())
        return clean_transaction_raw(
            b64encode(
                str(
                    json.dumps(
                        dict(
                            fee=self._fee,
                            raw=self._transaction.hexlify(),
                            outputs=dict(value=self._htlc_utxo["value"],
                                         tx_output_n=0,
                                         script=self._htlc_utxo["script"]),
                            network=self._network,
                            type=self._type,
                        ))).encode()).decode())
Пример #24
0
    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
Пример #25
0
    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
Пример #26
0
class RefundTransaction(Transaction):
    """
    Bitcoin Refund transaction.

    :param network: Bitcoin network, defaults to ``mainnet``.
    :type network: str
    :param version: Bitcoin transaction version, defaults to ``2``.
    :type version: int

    :returns: RefundTransaction -- Bitcoin refund transaction instance.

    .. warning::
        Do not forget to build transaction after initialize refund transaction.
    """
    def __init__(self,
                 network: str = config["network"],
                 version: int = config["version"]):
        super().__init__(network=network, version=version)

        self._transaction_hash: Optional[str] = None
        self._transaction_detail: Optional[dict] = None
        self._htlc_utxo: Optional[dict] = None
        self._interest: Optional[int] = None

    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 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 transaction_raw(self) -> str:
        """
        Get Bitcoin refund transaction raw.

        :returns: str -- Bitcoin refund transaction raw.

        >>> from swap.providers.bitcoin.transaction import RefundTransaction
        >>> refund_transaction: RefundTransaction = RefundTransaction("testnet")
        >>> refund_transaction.build_transaction(address="n1wgm6kkzMcNfAtJmes8YhpvtDzdNhDY5a", transaction_hash="a211d21110756b266925fee2fbf2dc81529beef5e410311b38578dc3a076fb31")
        >>> refund_transaction.transaction_raw()
        "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTMxZmI3NmEwYzM4ZDU3MzgxYjMxMTBlNGY1ZWU5YjUyODFkY2YyZmJlMmZlMjU2OTI2NmI3NTEwMTFkMjExYTIwMDAwMDAwMDAwZmZmZmZmZmYwMTYwODQwMTAwMDAwMDAwMDAxOTc2YTkxNGUwMGZmMmE2NDBiN2NlMmQzMzY4NjA3MzkxNjk0ODdhNTdmODRiMTU4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMDAsICJ0eF9vdXRwdXRfbiI6IDAsICJzY3JpcHQiOiAiYTkxNGM4Yzc3YTliNDNlZTJiZGYxYTA3YzQ4Njk5ODMzZDc2NjhiZjI2NGM4NyJ9LCAibmV0d29yayI6ICJ0ZXN0bmV0IiwgInR5cGUiOiAiYml0Y29pbl9yZWZ1bmRfdW5zaWduZWQifQ=="
        """

        # Check transaction
        if self._transaction is None:
            raise ValueError("Transaction is none, build transaction first.")

        # Encode refund transaction raw
        if self._type == "bitcoin_refund_signed":
            return clean_transaction_raw(
                b64encode(
                    str(
                        json.dumps(
                            dict(
                                raw=self._transaction.hexlify(),
                                fee=self._fee,
                                network=self._network,
                                type=self._type,
                            ))).encode()).decode())
        return clean_transaction_raw(
            b64encode(
                str(
                    json.dumps(
                        dict(
                            fee=self._fee,
                            raw=self._transaction.hexlify(),
                            outputs=dict(value=self._htlc_utxo["value"],
                                         tx_output_n=0,
                                         script=self._htlc_utxo["script"]),
                            network=self._network,
                            type=self._type,
                        ))).encode()).decode())
Пример #27
0
    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
Пример #28
0
    def sign_transaction(self, txins: Union[TxOut],
                         tx: MutableTransaction) -> MutableTransaction:
        '''sign the parent txn outputs P2PKH'''

        solver = P2pkhSolver(self._private_key)
        return tx.spend(txins, [solver for i in txins])
Пример #29
0
    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()
Пример #30
0
class FundTransaction(Transaction):
    """
    Bitcoin FundTransaction class.

    :param version: Bitcoin transaction version, defaults to 2.
    :type version: int
    :param network: Bitcoin network, defaults to testnet.
    :type network: str
    :returns: FundTransaction -- Bitcoin fund transaction instance.

    .. warning::
        Do not forget to build transaction after initialize fund transaction.
    """

    # Initialization fund transaction
    def __init__(self, version=2, network="testnet"):
        super().__init__(version=version, network=network)
        # Initialization wallet, htlc, amount and unspent
        self.wallet, self.htlc, self.amount, self.unspent = None, None, None, None
        # Getting previous transaction indexes using funding amount
        self.previous_transaction_indexes = None

    # Building transaction
    def build_transaction(self, wallet, htlc, amount, locktime=0):
        """
        Build Bitcoin fund transaction.

        :param wallet: Bitcoin sender wallet.
        :type wallet: bitcoin.wallet.Wallet
        :param htlc: Bitcoin hash time lock contract (HTLC).
        :type htlc: bitcoin.htlc.HTLC
        :param amount: Bitcoin amount to fund.
        :type amount: int
        :param locktime: Bitcoin transaction lock time, defaults to 0.
        :type locktime: int
        :returns: FundTransaction -- Bitcoin fund transaction instance.

        >>> from shuttle.providers.bitcoin.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

    # Signing fund transaction
    def sign(self, solver):
        """
        Sign Bitcoin fund transaction.

        :param solver: Bitcoin fund solver.
        :type solver: bitcoin.solver.FundSolver
        :returns: FundTransaction -- Bitcoin fund transaction instance.

        >>> from shuttle.providers.bitcoin.htlc import HTLC
        >>> from shuttle.providers.bitcoin.transaction import FundTransaction
        >>> from shuttle.providers.bitcoin.solver import FundSolver
        >>> 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_solver = FundSolver("92cbbc5990cb5090326a76feeb321cad01048635afe5756523bbf9f7a75bf38b")
        >>> fund_transaction = FundTransaction(network="testnet")
        >>> fund_transaction.build_transaction(sender_wallet, htlc, 10000)
        >>> fund_transaction.sign(solver=fund_solver)
        <shuttle.providers.bitcoin.transaction.FundTransaction object at 0x0409DAF0>
        """

        # Checking parameter instances
        if not isinstance(solver, FundSolver):
            raise TypeError(
                "invalid solver instance, only takes Bitcoin FundSolver class")
        if not self.unspent or not self.previous_transaction_indexes or not self.transaction:
            raise ValueError(
                "transaction script or unspent is none, build transaction first"
            )

        # Organizing outputs
        outputs = self.outputs(self.unspent, self.previous_transaction_indexes)
        # Signing fund transaction
        self.transaction.spend(outputs, [solver.solve() for _ in outputs])
        self._type = "bitcoin_fund_signed"
        return self

    # Automatically analysis previous transaction indexes using fund amount
    def get_previous_transaction_indexes(self, amount=None):
        if amount is None:
            amount = self.amount
        temp_amount = int()
        previous_transaction_indexes = list()
        for index, unspent in enumerate(self.unspent):
            temp_amount += unspent["amount"]
            if temp_amount > (amount + fee_calculator((index + 1), 2)):
                previous_transaction_indexes.append(index)
                break
            previous_transaction_indexes.append(index)
        return previous_transaction_indexes

    def unsigned_raw(self):
        """
        Get Bitcoin unsigned fund transaction raw.

        :returns: str -- Bitcoin unsigned fund transaction raw.

        >>> 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(sender_wallet, htlc, 10000)
        >>> fund_transaction.unsigned_raw()
        "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMTJjMzkyMjE3NDgzOTA2ZjkwMmU3M2M0YmMxMzI4NjRkZTU4MTUzNzcyZDc5MjY4OTYwOTk4MTYyMjY2NjM0YmUwMTAwMDAwMDAwZmZmZmZmZmYwMmU4MDMwMDAwMDAwMDAwMDAxN2E5MTQ5NzE4OTRjNThkODU5ODFjMTZjMjA1OWQ0MjJiY2RlMGIxNTZkMDQ0ODdhNjI5MDAwMDAwMDAwMDAwMTk3NmE5MTQ2YmNlNjVlNThhNTBiOTc5ODk5MzBlOWE0ZmYxYWMxYTc3NTE1ZWYxODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJhbW91bnQiOiAxMjM0MCwgIm4iOiAxLCAic2NyaXB0IjogIjc2YTkxNDZiY2U2NWU1OGE1MGI5Nzk4OTkzMGU5YTRmZjFhYzFhNzc1MTVlZjE4OGFjIn1dLCAidHlwZSI6ICJiaXRjb2luX2Z1bmRfdW5zaWduZWQifQ"
        """

        # Checking transaction and UTXO
        if not self.transaction or not self.unspent:
            raise ValueError(
                "transaction script or unspent is none, build transaction first"
            )

        outputs = []
        for index, utxo in enumerate(self.unspent):
            if self.previous_transaction_indexes is None or \
                    index in self.previous_transaction_indexes:
                outputs.append(
                    dict(amount=utxo["amount"],
                         n=utxo["output_index"],
                         script=utxo["script"]))

        # Encoding fund transaction raw
        return b64encode(
            str(
                json.dumps(
                    dict(fee=self._fee,
                         raw=self.transaction.hexlify(),
                         outputs=outputs,
                         network=self.network,
                         type="bitcoin_fund_unsigned"))).encode()).decode()