Exemple #1
0
    async def amplify_inputs(self, tx, new_keys=None, new_subs=None):
        lst = tx.sources[-1]
        orig_amount = lst.amount
        partial = orig_amount // (self.args.inputs + 1)

        amnt_sum = partial
        lst.amount = partial
        commitment = crypto.encodepoint(crypto.gen_c(crypto.decodeint(lst.mask), partial))
        lst.outputs[lst.real_output][1].mask = commitment

        for i in range(1, self.args.inputs + 1):
            is_lst = i >= self.args.inputs
            new_amnt = orig_amount - amnt_sum if is_lst else partial
            amnt_sum += partial

            commitment = crypto.encodepoint(crypto.gen_c(crypto.decodeint(lst.mask), new_amnt))
            new_inp = TxSourceEntry(outputs=list(lst.outputs),
                                    real_output=lst.real_output,
                                    real_out_tx_key=lst.real_out_tx_key,
                                    real_out_additional_tx_keys=lst.real_out_additional_tx_keys,
                                    real_output_in_tx_index=lst.real_output_in_tx_index,
                                    amount=new_amnt,
                                    rct=lst.rct,
                                    mask=lst.mask,
                                    multisig_kLRki=lst.multisig_kLRki)

            # Amount changed -> update the commitment
            orig_key = new_inp.outputs[new_inp.real_output][1]
            new_inp.outputs[new_inp.real_output] = (0, CtKey(dest=orig_key.dest, mask=commitment))

            # Randomize mixin values
            for i in range(new_inp.real_output + 1, len(new_inp.outputs)):
                new_inp.outputs[i] = (0, CtKey(
                    mask=crypto.encodepoint(self.random_pub()),
                    dest=crypto.encodepoint(self.random_pub())))

                if new_inp.real_out_additional_tx_keys:
                    new_inp.real_out_additional_tx_keys[i] = crypto.encodepoint(self.random_pub())

            self.check_input(new_inp, new_keys, new_subs)

            if not crypto.point_eq(
                    crypto.decodepoint(new_inp.outputs[new_inp.real_output][1].mask),
                    crypto.gen_c(crypto.decodeint(new_inp.mask), new_inp.amount),
                ): raise ValueError("Real source entry's mask does not equal spend key's")

            tx.sources.append(new_inp)
    async def commitment(self, in_amount):
        """
        Computes Pedersen commitment - pseudo outs
        Here is slight deviation from the original protocol.
        We want that \\sum Alpha = \\sum A_{i,j} where A_{i,j} is a mask from range proof for output i, bit j.

        Previously this was computed in such a way that Alpha_{last} = \\sum A{i,j} - \\sum_{i=0}^{last-1} Alpha
        But we would prefer to compute commitment before range proofs so alphas are generated completely randomly
        and the last A mask is computed in this special way.
        Returns pseudo_out
        :return:
        """
        alpha = crypto.random_scalar()
        self.sumpouts_alphas = crypto.sc_add(self.sumpouts_alphas, alpha)
        return alpha, crypto.gen_c(alpha, in_amount)
Exemple #3
0
async def verify_bp(bp_proof, amounts=None, masks=None):
    from monero_glue.xmr import bulletproof as bp

    if amounts:
        bp_proof.V = []
        for i in range(len(amounts)):
            C = crypto.gen_c(masks[i], amounts[i])
            crypto.scalarmult_into(C, C, crypto.sc_inv_eight())
            bp_proof.V.append(crypto.encodepoint(C))

    bpi = bp.BulletProofBuilder()
    res = bpi.verify(bp_proof)
    gc.collect()

    # Return as struct as the hash(BP_struct) != hash(BP_serialized)
    # as the original hashing does not take vector lengths into account which are dynamic
    # in the serialization scheme (and thus extraneous)
    return res
Exemple #4
0
    async def describe(self, inp, unsigned_txs, keys, key_subs):
        print("\nInp: %s, #txs: %s, #transfers: %s" % (inp, len(unsigned_txs.txes), len(unsigned_txs.transfers)))
        for txid, tx in enumerate(unsigned_txs.txes):
            srcs = tx.sources
            dsts = tx.splitted_dsts
            extra = tx.extra
            change = tx.change_dts
            account = tx.subaddr_account
            subs = tx.subaddr_indices
            mixin = len(srcs[0].outputs) - 1
            amnt_in = sum([x.amount for x in srcs])
            amnt_out = sum([x.amount for x in dsts])
            fee = amnt_in - amnt_out
            n_inp_additional = sum(
                [1 for x in srcs if len(x.real_out_additional_tx_keys) > 0]
            )

            change_addr = (
                addr.build_address(
                    change.addr.m_spend_public_key, change.addr.m_view_public_key
                )
                if change
                else None
            )
            out_txs2 = await self.reformat_outs(dsts)

            num_stdaddresses, num_subaddresses, single_dest_subaddress = addr.classify_subaddresses(
                out_txs2, change_addr
            )

            print(
                "  tx: %s, #inp: %2d, #inp_add: %2d, #out: %2d, mixin: %2d, acc: %s, subs: %s, "
                "xmr_in: %10.6f, xmr_out: %10.6f, fee: %10.6f, change: %10.6f, out_clean: %10.6f"
                % (
                    txid,
                    len(srcs),
                    n_inp_additional,
                    len(dsts),
                    mixin,
                    account,
                    subs,
                    wallet.conv_disp_amount(amnt_in),
                    wallet.conv_disp_amount(amnt_out),
                    wallet.conv_disp_amount(fee),
                    wallet.conv_disp_amount(change.amount) if change else 0,
                    wallet.conv_disp_amount(
                        (amnt_out - change.amount) if change else amnt_out
                    ),
                )
            )
            print(
                "  Out: num_std: %2d, num_sub: %2d, single_dest_sub: %s, total: %s"
                % (
                    num_stdaddresses,
                    num_subaddresses,
                    1 if single_dest_subaddress else 0,
                    len(dsts),
                )
            )

            accounts = set()
            subs = set()
            for inp in srcs:
                res = await self.analyze_input(keys, key_subs, inp)
                accounts.add(res[0])
                if res != (0, 0):
                    subs.add(res)

            print("  Ins: accounts: %s, subs: %s" % (accounts, len(subs)))

            extras = await monero.parse_extra_fields(extra)
            extras_val = []
            for c in extras:
                if isinstance(c, TxExtraPubKey):
                    extras_val.append("TxKey")
                elif isinstance(c, TxExtraNonce):
                    extras_val.append(
                        "Nonce: %s" % binascii.hexlify(c.nonce).decode("ascii")
                    )
                elif isinstance(c, TxExtraAdditionalPubKeys):
                    extras_val.append("AdditionalTxKeys: %s" % len(c.data))
                else:
                    extras_val.append(str(c))
            print("  Extras: %s" % ", ".join(extras_val))

            # Final verification
            for idx, inp in enumerate(tx.sources):
                self.check_input(inp, keys, key_subs)
                if not crypto.point_eq(
                        crypto.decodepoint(inp.outputs[inp.real_output][1].mask),
                        crypto.gen_c(crypto.decodeint(inp.mask), inp.amount),
                ): raise ValueError("Real source entry's mask does not equal spend key's. Inp: %d" % idx)
Exemple #5
0
    async def gen_rct_simple(
        self,
        in_sk,
        destinations,
        inamounts,
        outamounts,
        txn_fee,
        mix_ring,
        kLRki,
        msout,
        index,
    ):
        """
        Generate simple RCT signature.

        :param in_sk:
        :param destinations:
        :param inamounts:
        :param outamounts:
        :param txn_fee:
        :param mix_ring:
        :param kLRki:
        :param msout:
        :param index:
        :param out_sk:
        :return:
        """
        if len(inamounts) == 0:
            raise ValueError("Empty inamounts")
        if len(inamounts) != len(in_sk):
            raise ValueError("Different number of inamounts/inSk")
        if len(outamounts) != len(destinations):
            raise ValueError("Different number of amounts/destinations")
        if len(self.output_secrets) != len(destinations):
            raise ValueError("Different number of amount_keys/destinations")
        if len(index) != len(in_sk):
            raise ValueError("Different number of index/inSk")
        if len(mix_ring) != len(in_sk):
            raise ValueError("Different number of mixRing/inSk")
        for idx in range(len(mix_ring)):
            if index[idx] >= len(mix_ring[idx]):
                raise ValueError("Bad index into mixRing")

        rv, sumout, out_sk = await self.gen_rct_header(destinations, outamounts)
        rv.type = (
            xmrtypes.RctType.SimpleBulletproof
            if self.use_bulletproof
            else xmrtypes.RctType.Simple
        )
        rv.txnFee = txn_fee
        rv.mixRing = mix_ring

        # Pseudooutputs
        pseudo_outs = [None] * len(inamounts)
        rv.p.MGs = [None] * len(inamounts)
        sumpouts = crypto.sc_0()
        a = []
        for idx in range(len(inamounts) - 1):
            a.append(crypto.random_scalar())
            sumpouts = crypto.sc_add(sumpouts, a[idx])
            pseudo_outs[idx] = crypto.gen_c(a[idx], inamounts[idx])

        a.append(crypto.sc_sub(sumout, sumpouts))
        pseudo_outs[-1] = crypto.gen_c(a[-1], inamounts[-1])

        if self.use_bulletproof:
            rv.p.pseudoOuts = [crypto.encodepoint(x) for x in pseudo_outs]
        else:
            rv.pseudoOuts = [crypto.encodepoint(x) for x in pseudo_outs]

        full_message = await monero.get_pre_mlsag_hash(rv)

        # TODO: msout multisig
        for i in range(len(inamounts)):
            rv.p.MGs[i], msc = mlsag2.prove_rct_mg_simple(
                full_message,
                rv.mixRing[i],
                in_sk[i],
                a[i],
                pseudo_outs[i],
                kLRki[i] if kLRki else None,
                None,
                index[i],
            )

            if __debug__:
                assert mlsag2.ver_rct_mg_simple(
                    full_message, rv.p.MGs[i], rv.mixRing[i], pseudo_outs[i]
                )

        return rv
Exemple #6
0
    async def signature(self, tx):
        """
        Computes the signature in one pass.
        Implements RingCT in Python.

        :param tx: const data
        :type tx: xmrtypes.TxConstructionData
        :return:
        """
        amount_in = 0
        inamounts = [None] * len(self.source_permutation)
        index = [None] * len(self.source_permutation)
        in_sk = [None] * len(self.source_permutation)  # type: list[xmrtypes.CtKey]

        for i in range(len(self.source_permutation)):
            idx = self.source_permutation[i]
            src = tx.sources[idx]
            amount_in += src.amount
            inamounts[i] = src.amount
            index[i] = src.real_output
            in_sk[i] = xmrtypes.CtKey(
                dest=self.input_secrets[i][0], mask=crypto.decodeint(src.mask)
            )
            # TODO: kLRki

            # private key correctness test
            if __debug__:
                assert crypto.point_eq(
                    crypto.decodepoint(src.outputs[src.real_output][1].dest),
                    crypto.scalarmult_base(in_sk[i].dest),
                )
                assert crypto.point_eq(
                    crypto.decodepoint(src.outputs[src.real_output][1].mask),
                    crypto.gen_c(in_sk[i].mask, inamounts[i]),
                )

        destinations = []
        outamounts = []
        amount_out = 0
        for idx, dst in enumerate(tx.splitted_dsts):
            destinations.append(crypto.decodepoint(self.tx.vout[idx].target.key))
            outamounts.append(self.tx.vout[idx].amount)
            amount_out += self.tx.vout[idx].amount

        if self.use_simple_rct:
            mix_ring = [None] * (self.inp_idx + 1)
            for i in range(len(self.source_permutation)):
                src = tx.sources[self.source_permutation[i]]
                mix_ring[i] = []
                for idx2, out in enumerate(src.outputs):
                    mix_ring[i].append(out[1])

        else:
            n_total_outs = len(tx.sources[0].outputs)
            mix_ring = [None] * n_total_outs
            for idx in range(n_total_outs):
                mix_ring[idx] = []
                for i in range(len(self.source_permutation)):
                    src = tx.sources[self.source_permutation[i]]
                    mix_ring[idx].append(src.outputs[idx][1])

        if not self.use_simple_rct and amount_in > amount_out:
            outamounts.append(amount_in - amount_out)

        # Hide amounts
        self.zero_out_amounts()

        # Tx prefix hash
        await self.compute_tx_prefix_hash()

        # Signature
        if self.use_simple_rct:
            rv = await self.gen_rct_simple(
                in_sk,
                destinations,
                inamounts,
                outamounts,
                amount_in - amount_out,
                mix_ring,
                None,
                None,
                index,
            )
        else:
            rv = await self.gen_rct(
                in_sk,
                destinations,
                outamounts,
                mix_ring,
                None,
                None,
                tx.sources[0].real_output,
            )

        # Recode for serialization
        rv = monero.recode_rct(rv, encode=True)
        self.tx.signatures = []
        self.tx.rct_signatures = rv
        del rv

        # Serialize response
        writer = xmrserialize.MemoryReaderWriter()
        ar1 = xmrserialize.Archive(writer, True)
        await ar1.message(self.tx, msg_type=xmrtypes.Transaction)

        return bytes(writer.get_buffer())
    async def receive(self, tx, all_creds, con_data=None, exp_payment_id=None):
        """
        Test transaction receive with known view/spend keys of destinations.
        :param tx:
        :param all_creds:
        :param con_data:
        :param exp_payment_id:
        :return:
        """
        # Unserialize the transaction
        tx_obj = xmrtypes.Transaction()
        reader = xmrserialize.MemoryReaderWriter(bytearray(tx))
        ar1 = xmrserialize.Archive(reader, False)

        await ar1.message(tx_obj, msg_type=xmrtypes.Transaction)
        extras = await monero.parse_extra_fields(tx_obj.extra)
        tx_pub = monero.find_tx_extra_field_by_type(
            extras, xmrtypes.TxExtraPubKey
        ).pub_key
        additional_pub_keys = monero.find_tx_extra_field_by_type(
            extras, xmrtypes.TxExtraAdditionalPubKeys
        )
        num_outs = len(tx_obj.vout)
        num_received = 0

        # Try to receive tsx outputs with each account.
        tx_money_got_in_outs = collections.defaultdict(lambda: 0)
        outs = []

        change_idx = get_change_addr_idx(con_data.tsx_data.outputs, con_data.tsx_data.change_dts)

        for idx, creds in enumerate(all_creds):
            wallet_subs = {}
            for account in range(0, 5):
                monero.compute_subaddresses(creds, account, range(25), wallet_subs)

            derivation = crypto.generate_key_derivation(
                crypto.decodepoint(tx_pub), creds.view_key_private
            )
            additional_derivations = []
            if additional_pub_keys and additional_pub_keys.data:
                for x in additional_pub_keys.data:
                    additional_derivations.append(
                        crypto.generate_key_derivation(
                            crypto.decodepoint(x), creds.view_key_private
                        )
                    )

            for ti, to in enumerate(tx_obj.vout):
                tx_scan_info = monero.check_acc_out_precomp(
                    to, wallet_subs, derivation, additional_derivations, ti
                )
                if not tx_scan_info.received:
                    continue

                num_received += 1
                tx_scan_info = monero.scan_output(
                    creds, tx_obj, ti, tx_scan_info, tx_money_got_in_outs, outs, False
                )

                # Check spending private key correctness
                self.assertTrue(
                    crypto.point_eq(
                        crypto.decodepoint(tx_obj.rct_signatures.outPk[ti].mask),
                        crypto.gen_c(tx_scan_info.mask, tx_scan_info.amount),
                    )
                )

                self.assertTrue(
                    crypto.point_eq(
                        crypto.decodepoint(tx_obj.vout[ti].target.key),
                        crypto.scalarmult_base(tx_scan_info.in_ephemeral),
                    )
                )

                if exp_payment_id is not None:
                    payment_id = None
                    # Not checking payment id for change transaction
                    if exp_payment_id[0] == 1 and change_idx is not None and ti == change_idx:
                        continue

                    payment_id_type = None
                    extra_nonce = monero.find_tx_extra_field_by_type(extras, xmrtypes.TxExtraNonce)
                    if extra_nonce and monero.has_encrypted_payment_id(extra_nonce.nonce):
                        payment_id_type = 1
                        payment_id = monero.get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce)
                        payment_id = monero.encrypt_payment_id(payment_id, crypto.decodepoint(tx_pub), creds.view_key_private)

                    elif extra_nonce and monero.has_payment_id(extra_nonce.nonce):
                        payment_id_type = 0
                        payment_id = monero.get_payment_id_from_tx_extra_nonce(extra_nonce.nonce)

                    self.assertEqual(payment_id_type, exp_payment_id[0])
                    self.assertEqual(payment_id, exp_payment_id[1])

        # All outputs have to be successfully received
        self.assertEqual(num_outs, num_received)
    async def sign_input(
        self,
        src_entr,
        vini,
        hmac_vini,
        pseudo_out,
        pseudo_out_hmac,
        alpha_enc,
        spend_enc,
    ):
        """
        Generates a signature for one input.

        :param src_entr: Source entry
        :type src_entr: monero_glue.xmr.serialize_messages.tx_construct.TxSourceEntry
        :param vini: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero)
        :param hmac_vini: HMAC for the tx.vin[i] as returned from Trezor
        :param pseudo_out: pedersen commitment for the current input, uses alpha as the mask.
        Only in memory offloaded scenario. Tuple containing HMAC, as returned from the Trezor.
        :param pseudo_out_hmac:
        :param alpha_enc: alpha mask for the current input. Only in memory offloaded scenario,
        tuple as returned from the Trezor
        :param spend_enc:
        :return: Generated signature MGs[i]
        """
        self.state.set_signature()
        await self.trezor.iface.transaction_step(
            self.STEP_SIGN, self.inp_idx + 1, self.num_inputs()
        )

        self.inp_idx += 1
        if self.inp_idx >= self.num_inputs():
            raise ValueError("Invalid ins")
        if self.use_simple_rct and (not self.in_memory() and alpha_enc is None):
            raise ValueError("Inconsistent1")
        if self.use_simple_rct and (not self.in_memory() and pseudo_out is None):
            raise ValueError("Inconsistent2")
        if self.inp_idx >= 1 and not self.use_simple_rct:
            raise ValueError("Inconsistent3")

        inv_idx = self.source_permutation[self.inp_idx]

        # Check HMAC of all inputs
        hmac_vini_comp = await self.gen_hmac_vini(src_entr, vini, inv_idx)
        if not common.ct_equal(hmac_vini_comp, hmac_vini):
            raise ValueError("HMAC is not correct")

        gc.collect()
        self._log_trace(1)

        if self.use_simple_rct and not self.in_memory():
            pseudo_out_hmac_comp = crypto.compute_hmac(
                self.hmac_key_txin_comm(inv_idx), pseudo_out
            )
            if not common.ct_equal(pseudo_out_hmac_comp, pseudo_out_hmac):
                raise ValueError("HMAC is not correct")

            gc.collect()
            self._log_trace(2)

            from monero_glue.xmr.enc import chacha_poly

            alpha_c = crypto.decodeint(
                chacha_poly.decrypt_pack(
                    self.enc_key_txin_alpha(inv_idx), bytes(alpha_enc)
                )
            )
            pseudo_out_c = crypto.decodepoint(pseudo_out)

        elif self.use_simple_rct:
            alpha_c = self.input_alphas[self.inp_idx]
            pseudo_out_c = crypto.decodepoint(self.input_pseudo_outs[self.inp_idx])

        else:
            alpha_c = None
            pseudo_out_c = None

        # Spending secret
        if self.many_inputs():
            from monero_glue.xmr.enc import chacha_poly

            input_secret = crypto.decodeint(
                chacha_poly.decrypt_pack(self.enc_key_spend(inv_idx), bytes(spend_enc))
            )
        else:
            input_secret = self.input_secrets[self.inp_idx]

        gc.collect()
        self._log_trace(3)

        # Basic setup, sanity check
        index = src_entr.real_output
        in_sk = misc.StdObj(dest=input_secret, mask=crypto.decodeint(src_entr.mask))
        kLRki = src_entr.multisig_kLRki if self.multi_sig else None

        # Private key correctness test
        self.assrt(
            crypto.point_eq(
                crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].dest),
                crypto.scalarmult_base(in_sk.dest),
            ),
            "a1",
        )
        self.assrt(
            crypto.point_eq(
                crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].mask),
                crypto.gen_c(in_sk.mask, src_entr.amount),
            ),
            "a2",
        )

        gc.collect()
        self._log_trace(4)

        # RCT signature
        gc.collect()
        from monero_glue.xmr import mlsag2

        mg = None
        if self.use_simple_rct:
            # Simple RingCT
            mix_ring = [x[1] for x in src_entr.outputs]
            mg, msc = mlsag2.prove_rct_mg_simple(
                self.full_message,
                mix_ring,
                in_sk,
                alpha_c,
                pseudo_out_c,
                kLRki,
                None,
                index,
            )

            if __debug__:
                self.assrt(
                    mlsag2.ver_rct_mg_simple(
                        self.full_message, mg, mix_ring, pseudo_out_c
                    )
                )

        else:
            # Full RingCt, only one input
            txn_fee_key = crypto.scalarmult_h(self.get_fee())
            mix_ring = [[x[1]] for x in src_entr.outputs]
            mg, msc = mlsag2.prove_rct_mg(
                self.full_message,
                mix_ring,
                [in_sk],
                self.output_sk,
                self.output_pk,
                kLRki,
                None,
                index,
                txn_fee_key,
            )

            if __debug__:
                self.assrt(
                    mlsag2.ver_rct_mg(
                        mg, mix_ring, self.output_pk, txn_fee_key, self.full_message
                    )
                )

        gc.collect()
        self._log_trace(5)

        # Encode
        from monero_glue.xmr.sub.recode import recode_msg

        mgs = recode_msg([mg])
        cout = None

        gc.collect()
        self._log_trace(6)

        # Multisig values returned encrypted, keys returned after finished successfully.
        if self.multi_sig:
            from monero_glue.xmr.enc import chacha_poly

            cout = chacha_poly.encrypt_pack(self.enc_key_cout(), crypto.encodeint(msc))

        # Final state transition
        if self.inp_idx + 1 == self.num_inputs():
            self.state.set_signature_done()
            await self.trezor.iface.transaction_signed()

        gc.collect()
        self._log_trace()

        from monero_glue.messages.MoneroTransactionSignInputAck import (
            MoneroTransactionSignInputAck
        )

        return MoneroTransactionSignInputAck(
            signature=await misc.dump_msg_gc(mgs[0], preallocate=488, del_msg=True),
            cout=cout,
        )