def test_preimage_testdata(self):

        seed = bip39.seed(
            'alcohol woman abuse must during monitor noble actual mixed trade anger aisle',
            '')
        coin = coins.by_name(self.tx.coin_name)
        bip143 = Bitcoin(self.tx, None, coin)
        bip143.hash143_add_input(self.inp1)
        bip143.hash143_add_input(self.inp2)

        for txo in [self.out1, self.out2]:
            txo_bin = TxOutputBinType()
            txo_bin.amount = txo.amount
            script_pubkey = output_derive_script(txo.address, coin)
            bip143.hash143_add_output(txo_bin, script_pubkey)

        keychain = Keychain(seed, [[coin.curve_name, []]])
        node = keychain.derive(self.inp2.address_n)

        # test data public key hash
        # only for input 2 - input 1 is not segwit
        result = bip143.hash143_preimage_hash(self.inp2, [node.public_key()],
                                              1)
        self.assertEqual(
            hexlify(result),
            b'2fa3f1351618b2532228d7182d3221d95c21fd3d496e7e22e9ded873cf022a8b'
        )
Beispiel #2
0
    def test_bip143_outputs(self):
        seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
        coin = coins.by_name(self.tx.coin_name)
        bip143 = Bitcoin(self.tx, None, coin)

        for txo in [self.out1, self.out2]:
            txo_bin = TxOutputBinType()
            txo_bin.amount = txo.amount
            script_pubkey = output_derive_script(txo, coin)
            bip143.hash143_add_output(txo_bin, script_pubkey)

        outputs_hash = get_tx_hash(bip143.h_outputs, double=coin.sign_hash_double)
        self.assertEqual(hexlify(outputs_hash), b'de984f44532e2173ca0d64314fcefe6d30da6f8cf27bafa706da61df8a226c83')
    def test_outputs(self):

        seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
        coin = coins.by_name(self.tx.coin_name)
        bip143 = Bitcoin(self.tx, None, coin)

        for txo in [self.out1, self.out2]:
            txo_bin = TxOutputBinType()
            txo_bin.amount = txo.amount
            script_pubkey = output_derive_script(txo, coin)
            bip143.hash143_add_output(txo_bin, script_pubkey)

        outputs_hash = get_tx_hash(bip143.h_outputs, double=coin.sign_hash_double)
        self.assertEqual(hexlify(outputs_hash), b'863ef3e1a92afbfdb97f31ad0fc7683ee943e9abcf2501590ff8f6551f47e5e5')
Beispiel #4
0
    def test_bip143_preimage_testdata(self):
        seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
        coin = coins.by_name(self.tx.coin_name)
        bip143 = Bitcoin(self.tx, None, coin)
        bip143.hash143_add_input(self.inp1)
        for txo in [self.out1, self.out2]:
            txo_bin = TxOutputBinType()
            txo_bin.amount = txo.amount
            script_pubkey = output_derive_script(txo, coin)
            bip143.hash143_add_output(txo_bin, script_pubkey)

        # test data public key hash
        result = bip143.hash143_preimage_hash(self.inp1, unhexlify('79091972186c449eb1ded22b78e40d009bdf0089'))
        self.assertEqual(hexlify(result), b'64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6')
Beispiel #5
0
    def test_bip143_preimage_testdata(self):
        seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
        coin = coins.by_name(self.tx.coin_name)
        bip143 = Bitcoin(self.tx, None, coin)
        bip143.hash143_add_input(self.inp1)
        for txo in [self.out1, self.out2]:
            txo_bin = TxOutputBinType()
            txo_bin.amount = txo.amount
            script_pubkey = output_derive_script(txo.address, coin)
            bip143.hash143_add_output(txo_bin, script_pubkey)

        keychain = Keychain(seed, [[coin.curve_name, []]])
        node = keychain.derive(self.inp1.address_n)

        # test data public key hash
        result = bip143.hash143_preimage_hash(self.inp1, [node.public_key()], 1)
        self.assertEqual(hexlify(result), b'6e28aca7041720995d4acf59bbda64eef5d6f23723d23f2e994757546674bbd9')
    def test_preimage_testdata(self):

        seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
        coin = coins.by_name(self.tx.coin_name)
        bip143 = Bitcoin(self.tx, None, coin)
        bip143.hash143_add_input(self.inp1)
        bip143.hash143_add_input(self.inp2)

        for txo in [self.out1, self.out2]:
            txo_bin = TxOutputBinType()
            txo_bin.amount = txo.amount
            script_pubkey = output_derive_script(txo, coin)
            bip143.hash143_add_output(txo_bin, script_pubkey)

        # test data public key hash
        # only for input 2 - input 1 is not segwit
        result = bip143.hash143_preimage_hash(self.inp2, unhexlify('1d0f172a0ecb48aee1be1f2687d2963ae33f71a1'))
        self.assertEqual(hexlify(result), b'c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670')
    def test_zip243(self):
        coin = coins.by_name("Zcash")

        for v in self.VECTORS:
            tx = SignTx(
                coin_name="Zcash",
                inputs_count=len(v["inputs"]),
                outputs_count=len(v["outputs"]),
                version=v["version"],
                lock_time=v["lock_time"],
                expiry=v["expiry"],
                overwintered=(v["version"] >= 3),
                version_group_id=v["version_group_id"],
            )
            zip243 = Zip243()
            for i in v["inputs"]:
                txi = TxInputType()
                txi.amount = i["amount"]
                txi.prev_hash = unhexlify(i["prevout"][0])
                txi.prev_index = i["prevout"][1]
                txi.script_type = i["script_type"]
                txi.sequence = i["sequence"]
                zip243.add_prevouts(txi)
                zip243.add_sequence(txi)
            for o in v["outputs"]:
                txo = TxOutputBinType()
                txo.amount = o["amount"]
                txo.script_pubkey = unhexlify(o["script_pubkey"])
                zip243.add_output(txo)

            self.assertEqual(hexlify(zip243.get_prevouts_hash()),
                             v["prevouts_hash"])
            self.assertEqual(hexlify(zip243.get_sequence_hash()),
                             v["sequence_hash"])
            self.assertEqual(hexlify(zip243.get_outputs_hash()),
                             v["outputs_hash"])
            self.assertEqual(
                hexlify(
                    zip243.preimage_hash(coin, tx, txi,
                                         unhexlify(i["pubkeyhash"]),
                                         v["hash_type"])),
                v["preimage_hash"],
            )
Beispiel #8
0
    def test_zip143(self):
        coin = coins.by_name("Zcash")

        for v in self.VECTORS:
            tx = SignTx(
                coin_name="Zcash",
                inputs_count=len(v["inputs"]),
                outputs_count=len(v["outputs"]),
                version=v["version"],
                lock_time=v["lock_time"],
                expiry=v["expiry"],
                version_group_id=v["version_group_id"],
            )

            zip143 = Overwintered(tx, None, coin)

            for i in v["inputs"]:
                txi = TxInputType()
                txi.amount = i["amount"]
                txi.prev_hash = unhexlify(i["prevout"][0])
                txi.prev_index = i["prevout"][1]
                txi.script_type = i["script_type"]
                txi.sequence = i["sequence"]
                zip143.hash143_add_input(txi)
            for o in v["outputs"]:
                txo = TxOutputBinType()
                txo.amount = o["amount"]
                txo.script_pubkey = unhexlify(o["script_pubkey"])
                zip143.hash143_add_output(txo, txo.script_pubkey)

            self.assertEqual(hexlify(get_tx_hash(zip143.h_prevouts)),
                             v["prevouts_hash"])
            self.assertEqual(hexlify(get_tx_hash(zip143.h_sequence)),
                             v["sequence_hash"])
            self.assertEqual(hexlify(get_tx_hash(zip143.h_outputs)),
                             v["outputs_hash"])
            self.assertEqual(
                hexlify(
                    zip143.hash143_preimage_hash(txi,
                                                 unhexlify(i["pubkeyhash"]))),
                v["preimage_hash"],
            )
Beispiel #9
0
async def check_tx_fee(tx: SignTx, keychain: seed.Keychain):
    coin = coins.by_name(tx.coin_name)

    # h_first is used to make sure the inputs and outputs streamed in Phase 1
    # are the same as in Phase 2.  it is thus not required to fully hash the
    # tx, as the SignTx info is streamed only once
    h_first = utils.HashWriter(sha256())  # not a real tx hash

    if coin.decred:
        hash143 = decred.DecredPrefixHasher(
            tx)  # pseudo BIP-0143 prefix hashing
        tx_ser = TxRequestSerializedType()
    elif tx.overwintered:
        if tx.version == 3:
            hash143 = zcash.Zip143()  # ZIP-0143 transaction hashing
        elif tx.version == 4:
            hash143 = zcash.Zip243()  # ZIP-0243 transaction hashing
        else:
            raise SigningError(
                FailureType.DataError,
                "Unsupported version for overwintered transaction",
            )
    else:
        hash143 = segwit_bip143.Bip143()  # BIP-0143 transaction hashing

    multifp = multisig.MultisigFingerprint(
    )  # control checksum of multisig inputs
    weight = tx_weight.TxWeightCalculator(tx.inputs_count, tx.outputs_count)

    total_in = 0  # sum of input amounts
    segwit_in = 0  # sum of segwit input amounts
    total_out = 0  # sum of output amounts
    change_out = 0  # change output amount
    wallet_path = []  # common prefix of input paths
    segwit = {}  # dict of booleans stating if input is segwit

    # output structures
    txo_bin = TxOutputBinType()
    tx_req = TxRequest()
    tx_req.details = TxRequestDetailsType()

    for i in range(tx.inputs_count):
        progress.advance()
        # STAGE_REQUEST_1_INPUT
        txi = await helpers.request_tx_input(tx_req, i)
        wallet_path = input_extract_wallet_path(txi, wallet_path)
        writers.write_tx_input_check(h_first, txi)
        weight.add_input(txi)
        hash143.add_prevouts(
            txi)  # all inputs are included (non-segwit as well)
        hash143.add_sequence(txi)

        if not addresses.validate_full_path(txi.address_n, coin,
                                            txi.script_type):
            await helpers.confirm_foreign_address(txi.address_n)

        if txi.multisig:
            multifp.add(txi.multisig)

        if txi.script_type in (
                InputScriptType.SPENDWITNESS,
                InputScriptType.SPENDP2SHWITNESS,
        ):
            if not coin.segwit:
                raise SigningError(FailureType.DataError,
                                   "Segwit not enabled on this coin")
            if not txi.amount:
                raise SigningError(FailureType.DataError,
                                   "Segwit input without amount")
            segwit[i] = True
            segwit_in += txi.amount
            total_in += txi.amount

        elif txi.script_type in (
                InputScriptType.SPENDADDRESS,
                InputScriptType.SPENDMULTISIG,
        ):
            if coin.force_bip143 or tx.overwintered:
                if not txi.amount:
                    raise SigningError(FailureType.DataError,
                                       "Expected input with amount")
                segwit[i] = False
                segwit_in += txi.amount
                total_in += txi.amount
            else:
                segwit[i] = False
                total_in += await get_prevtx_output_value(
                    coin, tx_req, txi.prev_hash, txi.prev_index)

        else:
            raise SigningError(FailureType.DataError,
                               "Wrong input script type")

        if coin.decred:
            w_txi = writers.empty_bytearray(8 if i == 0 else 0 + 9 +
                                            len(txi.prev_hash))
            if i == 0:  # serializing first input => prepend headers
                writers.write_bytes(w_txi, get_tx_header(coin, tx))
            writers.write_tx_input_decred(w_txi, txi)
            tx_ser.serialized_tx = w_txi
            tx_req.serialized = tx_ser

    if coin.decred:
        hash143.add_output_count(tx)

    for o in range(tx.outputs_count):
        # STAGE_REQUEST_3_OUTPUT
        txo = await helpers.request_tx_output(tx_req, o)
        txo_bin.amount = txo.amount
        txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)
        weight.add_output(txo_bin.script_pubkey)

        if change_out == 0 and output_is_change(txo, wallet_path, segwit_in,
                                                multifp):
            # output is change and does not need confirmation
            change_out = txo.amount
        elif not await helpers.confirm_output(txo, coin):
            raise SigningError(FailureType.ActionCancelled, "Output cancelled")

        if coin.decred:
            if txo.decred_script_version is not None and txo.decred_script_version != 0:
                raise SigningError(
                    FailureType.ActionCancelled,
                    "Cannot send to output with script version != 0",
                )
            txo_bin.decred_script_version = txo.decred_script_version

            w_txo_bin = writers.empty_bytearray(4 + 8 + 2 + 4 +
                                                len(txo_bin.script_pubkey))
            if o == 0:  # serializing first output => prepend outputs count
                writers.write_varint(w_txo_bin, tx.outputs_count)
            writers.write_tx_output(w_txo_bin, txo_bin)
            tx_ser.serialized_tx = w_txo_bin
            tx_req.serialized = tx_ser
            hash143.set_last_output_bytes(w_txo_bin)

        writers.write_tx_output(h_first, txo_bin)
        hash143.add_output(txo_bin)
        total_out += txo_bin.amount

    fee = total_in - total_out
    if fee < 0:
        raise SigningError(FailureType.NotEnoughFunds, "Not enough funds")

    # fee > (coin.maxfee per byte * tx size)
    if fee > (coin.maxfee_kb / 1000) * (weight.get_total() / 4):
        if not await helpers.confirm_feeoverthreshold(fee, coin):
            raise SigningError(FailureType.ActionCancelled,
                               "Signing cancelled")

    if not await helpers.confirm_total(total_in - change_out, fee, coin):
        raise SigningError(FailureType.ActionCancelled, "Total cancelled")

    if coin.decred:
        hash143.add_locktime_expiry(tx)

    return h_first, hash143, segwit, total_in, wallet_path
Beispiel #10
0
async def sign_tx(tx: SignTx, keychain: seed.Keychain):
    tx = helpers.sanitize_sign_tx(tx)

    progress.init(tx.inputs_count, tx.outputs_count)

    # Phase 1

    h_first, hash143, segwit, authorized_in, wallet_path = await check_tx_fee(
        tx, keychain)

    # Phase 2
    # - sign inputs
    # - check that nothing changed

    coin = coins.by_name(tx.coin_name)
    tx_ser = TxRequestSerializedType()

    txo_bin = TxOutputBinType()
    tx_req = TxRequest()
    tx_req.details = TxRequestDetailsType()
    tx_req.serialized = None

    if coin.decred:
        prefix_hash = hash143.prefix_hash()

    for i_sign in range(tx.inputs_count):
        progress.advance()
        txi_sign = None
        key_sign = None
        key_sign_pub = None

        if segwit[i_sign]:
            # STAGE_REQUEST_SEGWIT_INPUT
            txi_sign = await helpers.request_tx_input(tx_req, i_sign)

            if not input_is_segwit(txi_sign):
                raise SigningError(FailureType.ProcessError,
                                   "Transaction has changed during signing")
            input_check_wallet_path(txi_sign, wallet_path)

            key_sign = keychain.derive(txi_sign.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()
            txi_sign.script_sig = input_derive_script(coin, txi_sign,
                                                      key_sign_pub)

            w_txi = writers.empty_bytearray(7 + len(txi_sign.prev_hash) + 4 +
                                            len(txi_sign.script_sig) + 4)
            if i_sign == 0:  # serializing first input => prepend headers
                writers.write_bytes(w_txi, get_tx_header(coin, tx, True))
            writers.write_tx_input(w_txi, txi_sign)
            tx_ser.serialized_tx = w_txi
            tx_req.serialized = tx_ser

        elif coin.force_bip143 or tx.overwintered:
            # STAGE_REQUEST_SEGWIT_INPUT
            txi_sign = await helpers.request_tx_input(tx_req, i_sign)
            input_check_wallet_path(txi_sign, wallet_path)

            is_bip143 = (txi_sign.script_type == InputScriptType.SPENDADDRESS
                         or txi_sign.script_type
                         == InputScriptType.SPENDMULTISIG)
            if not is_bip143 or txi_sign.amount > authorized_in:
                raise SigningError(FailureType.ProcessError,
                                   "Transaction has changed during signing")
            authorized_in -= txi_sign.amount

            key_sign = keychain.derive(txi_sign.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()
            hash143_hash = hash143.preimage_hash(
                coin,
                tx,
                txi_sign,
                addresses.ecdsa_hash_pubkey(key_sign_pub, coin),
                get_hash_type(coin),
            )

            # if multisig, check if signing with a key that is included in multisig
            if txi_sign.multisig:
                multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub)

            signature = ecdsa_sign(key_sign, hash143_hash)
            tx_ser.signature_index = i_sign
            tx_ser.signature = signature

            # serialize input with correct signature
            txi_sign.script_sig = input_derive_script(coin, txi_sign,
                                                      key_sign_pub, signature)
            w_txi_sign = writers.empty_bytearray(5 + len(txi_sign.prev_hash) +
                                                 4 + len(txi_sign.script_sig) +
                                                 4)
            if i_sign == 0:  # serializing first input => prepend headers
                writers.write_bytes(w_txi_sign, get_tx_header(coin, tx))
            writers.write_tx_input(w_txi_sign, txi_sign)
            tx_ser.serialized_tx = w_txi_sign

            tx_req.serialized = tx_ser

        elif coin.decred:
            txi_sign = await helpers.request_tx_input(tx_req, i_sign)

            input_check_wallet_path(txi_sign, wallet_path)

            key_sign = keychain.derive(txi_sign.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()

            if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
                prev_pkscript = scripts.output_script_multisig(
                    multisig.multisig_get_pubkeys(txi_sign.multisig),
                    txi_sign.multisig.m,
                )
            elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
                prev_pkscript = scripts.output_script_p2pkh(
                    addresses.ecdsa_hash_pubkey(key_sign_pub, coin))
            else:
                raise ValueError("Unknown input script type")

            h_witness = utils.HashWriter(blake256())
            writers.write_uint32(
                h_witness,
                tx.version | decred.DECRED_SERIALIZE_WITNESS_SIGNING)
            writers.write_varint(h_witness, tx.inputs_count)

            for ii in range(tx.inputs_count):
                if ii == i_sign:
                    writers.write_varint(h_witness, len(prev_pkscript))
                    writers.write_bytes(h_witness, prev_pkscript)
                else:
                    writers.write_varint(h_witness, 0)

            witness_hash = writers.get_tx_hash(h_witness,
                                               double=coin.sign_hash_double,
                                               reverse=False)

            h_sign = utils.HashWriter(blake256())
            writers.write_uint32(h_sign, decred.DECRED_SIGHASHALL)
            writers.write_bytes(h_sign, prefix_hash)
            writers.write_bytes(h_sign, witness_hash)

            sig_hash = writers.get_tx_hash(h_sign,
                                           double=coin.sign_hash_double)
            signature = ecdsa_sign(key_sign, sig_hash)
            tx_ser.signature_index = i_sign
            tx_ser.signature = signature

            # serialize input with correct signature
            txi_sign.script_sig = input_derive_script(coin, txi_sign,
                                                      key_sign_pub, signature)
            w_txi_sign = writers.empty_bytearray(
                8 + 4 +
                len(hash143.get_last_output_bytes()) if i_sign == 0 else 0 +
                16 + 4 + len(txi_sign.script_sig))

            if i_sign == 0:
                writers.write_bytes(w_txi_sign,
                                    hash143.get_last_output_bytes())
                writers.write_uint32(w_txi_sign, tx.lock_time)
                writers.write_uint32(w_txi_sign, tx.expiry)
                writers.write_varint(w_txi_sign, tx.inputs_count)

            writers.write_tx_input_decred_witness(w_txi_sign, txi_sign)
            tx_ser.serialized_tx = w_txi_sign
            tx_req.serialized = tx_ser

        else:
            # hash of what we are signing with this input
            h_sign = utils.HashWriter(sha256())
            # same as h_first, checked before signing the digest
            h_second = utils.HashWriter(sha256())

            if tx.overwintered:
                writers.write_uint32(
                    h_sign, tx.version
                    | zcash.OVERWINTERED)  # nVersion | fOverwintered
                writers.write_uint32(h_sign,
                                     tx.version_group_id)  # nVersionGroupId
            else:
                writers.write_uint32(h_sign, tx.version)  # nVersion
                if tx.timestamp:
                    writers.write_uint32(h_sign, tx.timestamp)

            writers.write_varint(h_sign, tx.inputs_count)

            for i in range(tx.inputs_count):
                # STAGE_REQUEST_4_INPUT
                txi = await helpers.request_tx_input(tx_req, i)
                input_check_wallet_path(txi, wallet_path)
                writers.write_tx_input_check(h_second, txi)
                if i == i_sign:
                    txi_sign = txi
                    key_sign = keychain.derive(txi.address_n, coin.curve_name)
                    key_sign_pub = key_sign.public_key()
                    # for the signing process the script_sig is equal
                    # to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH)
                    if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
                        txi_sign.script_sig = scripts.output_script_multisig(
                            multisig.multisig_get_pubkeys(txi_sign.multisig),
                            txi_sign.multisig.m,
                        )
                    elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
                        txi_sign.script_sig = scripts.output_script_p2pkh(
                            addresses.ecdsa_hash_pubkey(key_sign_pub, coin))
                        if coin.bip115:
                            txi_sign.script_sig += scripts.script_replay_protection_bip115(
                                txi_sign.prev_block_hash_bip115,
                                txi_sign.prev_block_height_bip115,
                            )
                    else:
                        raise SigningError(FailureType.ProcessError,
                                           "Unknown transaction type")
                else:
                    txi.script_sig = bytes()
                writers.write_tx_input(h_sign, txi)

            writers.write_varint(h_sign, tx.outputs_count)

            for o in range(tx.outputs_count):
                # STAGE_REQUEST_4_OUTPUT
                txo = await helpers.request_tx_output(tx_req, o)
                txo_bin.amount = txo.amount
                txo_bin.script_pubkey = output_derive_script(
                    txo, coin, keychain)
                writers.write_tx_output(h_second, txo_bin)
                writers.write_tx_output(h_sign, txo_bin)

            writers.write_uint32(h_sign, tx.lock_time)
            if tx.overwintered:
                writers.write_uint32(h_sign, tx.expiry)  # expiryHeight
                writers.write_varint(h_sign, 0)  # nJoinSplit

            writers.write_uint32(h_sign, get_hash_type(coin))

            # check the control digests
            if writers.get_tx_hash(h_first,
                                   False) != writers.get_tx_hash(h_second):
                raise SigningError(FailureType.ProcessError,
                                   "Transaction has changed during signing")

            # if multisig, check if signing with a key that is included in multisig
            if txi_sign.multisig:
                multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub)

            # compute the signature from the tx digest
            signature = ecdsa_sign(
                key_sign,
                writers.get_tx_hash(h_sign, double=coin.sign_hash_double))
            tx_ser.signature_index = i_sign
            tx_ser.signature = signature

            # serialize input with correct signature
            txi_sign.script_sig = input_derive_script(coin, txi_sign,
                                                      key_sign_pub, signature)
            w_txi_sign = writers.empty_bytearray(5 + len(txi_sign.prev_hash) +
                                                 4 + len(txi_sign.script_sig) +
                                                 4)
            if i_sign == 0:  # serializing first input => prepend headers
                writers.write_bytes(w_txi_sign, get_tx_header(coin, tx))
            writers.write_tx_input(w_txi_sign, txi_sign)
            tx_ser.serialized_tx = w_txi_sign

            tx_req.serialized = tx_ser

    if coin.decred:
        return await helpers.request_tx_finish(tx_req)

    for o in range(tx.outputs_count):
        progress.advance()
        # STAGE_REQUEST_5_OUTPUT
        txo = await helpers.request_tx_output(tx_req, o)
        txo_bin.amount = txo.amount
        txo_bin.script_pubkey = output_derive_script(txo, coin, keychain)

        # serialize output
        w_txo_bin = writers.empty_bytearray(5 + 8 + 5 +
                                            len(txo_bin.script_pubkey) + 4)
        if o == 0:  # serializing first output => prepend outputs count
            writers.write_varint(w_txo_bin, tx.outputs_count)
        writers.write_tx_output(w_txo_bin, txo_bin)

        tx_ser.signature_index = None
        tx_ser.signature = None
        tx_ser.serialized_tx = w_txo_bin

        tx_req.serialized = tx_ser

    any_segwit = True in segwit.values()

    for i in range(tx.inputs_count):
        progress.advance()
        if segwit[i]:
            # STAGE_REQUEST_SEGWIT_WITNESS
            txi = await helpers.request_tx_input(tx_req, i)
            input_check_wallet_path(txi, wallet_path)

            if not input_is_segwit(txi) or txi.amount > authorized_in:
                raise SigningError(FailureType.ProcessError,
                                   "Transaction has changed during signing")
            authorized_in -= txi.amount

            key_sign = keychain.derive(txi.address_n, coin.curve_name)
            key_sign_pub = key_sign.public_key()
            hash143_hash = hash143.preimage_hash(
                coin,
                tx,
                txi,
                addresses.ecdsa_hash_pubkey(key_sign_pub, coin),
                get_hash_type(coin),
            )

            signature = ecdsa_sign(key_sign, hash143_hash)
            if txi.multisig:
                # find out place of our signature based on the pubkey
                signature_index = multisig.multisig_pubkey_index(
                    txi.multisig, key_sign_pub)
                witness = scripts.witness_p2wsh(txi.multisig, signature,
                                                signature_index,
                                                get_hash_type(coin))
            else:
                witness = scripts.witness_p2wpkh(signature, key_sign_pub,
                                                 get_hash_type(coin))

            tx_ser.serialized_tx = witness
            tx_ser.signature_index = i
            tx_ser.signature = signature
        elif any_segwit:
            tx_ser.serialized_tx = bytearray(
                1)  # empty witness for non-segwit inputs
            tx_ser.signature_index = None
            tx_ser.signature = None

        tx_req.serialized = tx_ser

    writers.write_uint32(tx_ser.serialized_tx, tx.lock_time)

    if tx.overwintered:
        if tx.version == 3:
            writers.write_uint32(tx_ser.serialized_tx,
                                 tx.expiry)  # expiryHeight
            writers.write_varint(tx_ser.serialized_tx, 0)  # nJoinSplit
        elif tx.version == 4:
            writers.write_uint32(tx_ser.serialized_tx,
                                 tx.expiry)  # expiryHeight
            writers.write_uint64(tx_ser.serialized_tx, 0)  # valueBalance
            writers.write_varint(tx_ser.serialized_tx, 0)  # nShieldedSpend
            writers.write_varint(tx_ser.serialized_tx, 0)  # nShieldedOutput
            writers.write_varint(tx_ser.serialized_tx, 0)  # nJoinSplit
        else:
            raise SigningError(
                FailureType.DataError,
                "Unsupported version for overwintered transaction",
            )

    await helpers.request_tx_finish(tx_req)
Beispiel #11
0
async def sign_tx(tx: SignTx, root):

    tx = sanitize_sign_tx(tx)
    coin = coins.by_name(tx.coin_name)

    # Phase 1
    # - check inputs, previous transactions, and outputs
    # - ask for confirmations
    # - check fee

    total_in = 0  # sum of input amounts
    total_out = 0  # sum of output amounts
    change_out = 0  # change output amount

    # h_first is used to make sure the inputs and outputs streamed in Phase 1
    # are the same as in Phase 2.  it is thus not required to fully hash the
    # tx, as the SignTx info is streamed only once
    h_first = HashWriter(sha256)  # not a real tx hash

    txo_bin = TxOutputBinType()
    tx_req = TxRequest()
    tx_req.details = TxRequestDetailsType()

    for i in range(tx.inputs_count):
        # STAGE_REQUEST_1_INPUT
        txi = await request_tx_input(tx_req, i)
        write_tx_input_check(h_first, txi)
        total_in += await get_prevtx_output_value(
            tx_req, txi.prev_hash, txi.prev_index)

    for o in range(tx.outputs_count):
        # STAGE_REQUEST_3_OUTPUT
        txo = await request_tx_output(tx_req, o)
        if output_is_change(txo):
            if change_out != 0:
                raise SigningError(FailureType.ProcessError,
                                   'Only one change output is valid')
            change_out = txo.amount
        else:
            if not await confirm_output(txo, coin):
                raise SigningError(FailureType.ActionCancelled,
                                   'Output cancelled')
        txo_bin.amount = txo.amount
        txo_bin.script_pubkey = output_derive_script(txo, coin, root)
        write_tx_output(h_first, txo_bin)
        total_out += txo_bin.amount

    fee = total_in - total_out

    if fee < 0:
        raise SigningError(FailureType.NotEnoughFunds,
                           'Not enough funds')

    if fee > coin.maxfee_kb * ((estimate_tx_size(tx.inputs_count, tx.outputs_count) + 999) // 1000):
        if not await confirm_feeoverthreshold(fee, coin):
            raise SigningError(FailureType.ActionCancelled,
                               'Signing cancelled')

    if not await confirm_total(total_out - change_out, fee, coin):
        raise SigningError(FailureType.ActionCancelled,
                           'Total cancelled')

    # Phase 2
    # - sign inputs
    # - check that nothing changed

    tx_ser = TxRequestSerializedType()

    for i_sign in range(tx.inputs_count):
        # hash of what we are signing with this input
        h_sign = HashWriter(sha256)
        # same as h_first, checked at the end of this iteration
        h_second = HashWriter(sha256)

        txi_sign = None
        key_sign = None
        key_sign_pub = None

        write_uint32(h_sign, tx.version)

        write_varint(h_sign, tx.inputs_count)

        for i in range(tx.inputs_count):
            # STAGE_REQUEST_4_INPUT
            txi = await request_tx_input(tx_req, i)
            write_tx_input_check(h_second, txi)
            if i == i_sign:
                txi_sign = txi
                key_sign = node_derive(root, txi.address_n)
                key_sign_pub = key_sign.public_key()
                txi.script_sig = input_derive_script(txi, key_sign_pub)
            else:
                txi.script_sig = bytes()
            write_tx_input(h_sign, txi)

        write_varint(h_sign, tx.outputs_count)

        for o in range(tx.outputs_count):
            # STAGE_REQUEST_4_OUTPUT
            txo = await request_tx_output(tx_req, o)
            txo_bin.amount = txo.amount
            txo_bin.script_pubkey = output_derive_script(txo, coin, root)
            write_tx_output(h_second, txo_bin)
            write_tx_output(h_sign, txo_bin)

        write_uint32(h_sign, tx.lock_time)

        write_uint32(h_sign, 0x00000001)  # SIGHASH_ALL hash_type

        # check the control digests
        if get_tx_hash(h_first, False) != get_tx_hash(h_second, False):
            raise SigningError(FailureType.ProcessError,
                               'Transaction has changed during signing')

        # compute the signature from the tx digest
        signature = ecdsa_sign(key_sign, get_tx_hash(h_sign, True))
        tx_ser.signature_index = i_sign
        tx_ser.signature = signature

        # serialize input with correct signature
        txi_sign.script_sig = input_derive_script(
            txi_sign, key_sign_pub, signature)
        w_txi_sign = bytearray_with_cap(
            len(txi_sign.prev_hash) + 4 + 5 + len(txi_sign.script_sig) + 4)
        if i_sign == 0:  # serializing first input => prepend tx version and inputs count
            write_uint32(w_txi_sign, tx.version)
            write_varint(w_txi_sign, tx.inputs_count)
        write_tx_input(w_txi_sign, txi_sign)
        tx_ser.serialized_tx = w_txi_sign

        tx_req.serialized = tx_ser

    for o in range(tx.outputs_count):
        # STAGE_REQUEST_5_OUTPUT
        txo = await request_tx_output(tx_req, o)
        txo_bin.amount = txo.amount
        txo_bin.script_pubkey = output_derive_script(txo, coin, root)

        # serialize output
        w_txo_bin = bytearray_with_cap(
            5 + 8 + 5 + len(txo_bin.script_pubkey) + 4)
        if o == 0:  # serializing first output => prepend outputs count
            write_varint(w_txo_bin, tx.outputs_count)
        write_tx_output(w_txo_bin, txo_bin)
        if o == tx.outputs_count - 1:  # serializing last output => append tx lock_time
            write_uint32(w_txo_bin, tx.lock_time)
        tx_ser.signature_index = None
        tx_ser.signature = None
        tx_ser.serialized_tx = w_txo_bin

        tx_req.serialized = tx_ser

    await request_tx_finish(tx_req)