def test_scalarmult_base(self):
        scalar = crypto.decodeint(
            unhexlify(
                b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303"
            )
        )
        exp = unhexlify(
            b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09"
        )

        res = crypto.scalarmult_base(scalar)
        self.assertEqual(exp, crypto.encodepoint(res))
        self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))

        scalar = crypto.decodeint(
            unhexlify(
                b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f"
            )
        )
        exp = unhexlify(
            b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5"
        )

        res = crypto.scalarmult_base(scalar)
        self.assertEqual(exp, crypto.encodepoint(res))
        self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))
Example #2
0
def generate_mlsag_simple(message, pubs, in_sk, a, cout, kLRki, index):
    """
    MLSAG for RctType.Simple
    :param message: the full message to be signed (actually its hash)
    :param pubs: vector of MoneroRctKey; this forms the ring; point values in encoded form; (dest, mask) = (P, C)
    :param in_sk: CtKey; spending private key with input commitment mask (original); better_name: input_secret_key
    :param a: mask from the pseudo output commitment; better name: pseudo_out_alpha
    :param cout: pseudo output commitment; point, decoded; better name: pseudo_out_c
    :param kLRki: used only in multisig, currently not implemented
    :param index: specifies corresponding public key to the `in_sk` in the pubs array
    :return: MgSig
    """
    # Monero signs inputs separately, so `rows` always equals 2 (pubkey, commitment)
    # and `dsRows` is always 1 (denotes where the pubkeys "end")
    rows = 2
    dsRows = 1
    cols = len(pubs)
    if cols == 0:
        raise ValueError("Empty pubs")

    sk = _key_vector(rows)
    M = _key_matrix(rows, cols)

    sk[0] = in_sk.dest
    sk[1] = crypto.sc_sub(in_sk.mask, a)

    for i in range(cols):
        M[i][0] = crypto.decodepoint(pubs[i].dest)
        M[i][1] = crypto.point_sub(crypto.decodepoint(pubs[i].commitment),
                                   cout)

    return generate_mlsag(message, M, sk, kLRki, index, dsRows)
Example #3
0
def export_key_image(creds: AccountCreds, subaddresses: Subaddresses,
                     td: MoneroTransferDetails) -> tuple[Ge25519, Sig]:
    out_key = crypto.decodepoint(td.out_key)
    tx_pub_key = crypto.decodepoint(td.tx_pub_key)

    additional_tx_pub_key = None
    if len(td.additional_tx_pub_keys) == 1:  # compression
        additional_tx_pub_key = crypto.decodepoint(
            td.additional_tx_pub_keys[0])
    elif td.additional_tx_pub_keys:
        if td.internal_output_index >= len(td.additional_tx_pub_keys):
            raise ValueError("Wrong number of additional derivations")
        additional_tx_pub_key = crypto.decodepoint(
            td.additional_tx_pub_keys[td.internal_output_index])

    ki, sig = _export_key_image(
        creds,
        subaddresses,
        out_key,
        tx_pub_key,
        additional_tx_pub_key,
        td.internal_output_index,
        True,
        td.sub_addr_major,
        td.sub_addr_minor,
    )
    return ki, sig
Example #4
0
    def gen_clsag_sig(self, ring_size=11, index=None):
        msg = random.bytes(32)
        amnt = crypto.sc_init(random.uniform(0xFFFFFF) + 12)
        priv = crypto.random_scalar()
        msk = crypto.random_scalar()
        alpha = crypto.random_scalar()
        P = crypto.scalarmult_base(priv)
        C = crypto.add_keys2(msk, amnt, crypto.xmr_H())
        Cp = crypto.add_keys2(alpha, amnt, crypto.xmr_H())

        ring = []
        for i in range(ring_size - 1):
            tk = TmpKey(
                crypto.encodepoint(
                    crypto.scalarmult_base(crypto.random_scalar())),
                crypto.encodepoint(
                    crypto.scalarmult_base(crypto.random_scalar())),
            )
            ring.append(tk)

        index = index if index is not None else random.uniform(len(ring))
        ring.insert(index, TmpKey(crypto.encodepoint(P),
                                  crypto.encodepoint(C)))
        ring2 = list(ring)
        mg_buffer = []

        self.assertTrue(
            crypto.point_eq(crypto.scalarmult_base(priv),
                            crypto.decodepoint(ring[index].dest)))
        self.assertTrue(
            crypto.point_eq(
                crypto.scalarmult_base(crypto.sc_sub(msk, alpha)),
                crypto.point_sub(crypto.decodepoint(ring[index].commitment),
                                 Cp),
            ))

        mlsag.generate_clsag_simple(
            msg,
            ring,
            CtKey(priv, msk),
            alpha,
            Cp,
            index,
            mg_buffer,
        )

        sD = crypto.decodepoint(mg_buffer[-1])
        sc1 = crypto.decodeint(mg_buffer[-2])
        scalars = [crypto.decodeint(x) for x in mg_buffer[1:-2]]
        H = crypto.new_point()
        sI = crypto.new_point()

        crypto.hash_to_point_into(H, crypto.encodepoint(P))
        crypto.scalarmult_into(sI, H, priv)  # I = p*H
        return msg, scalars, sc1, sI, sD, ring2, Cp
 def test_encoding(self):
     point = unhexlify(
         b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0"
     )
     self.assertEqual(point, crypto.encodepoint(crypto.decodepoint(point)))
     self.assertTrue(
         crypto.point_eq(
             crypto.decodepoint(point),
             crypto.decodepoint(
                 crypto.encodepoint(crypto.decodepoint(point))),
         ))
    def test_public_spend(self):
        derivation = unhexlify(
            b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01"
        )
        base = unhexlify(
            b"7d996b0f2db6dbb5f2a086211f2399a4a7479b2c911af307fdc3f7f61a88cb0e"
        )
        pkey_ex = unhexlify(
            b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546"
        )

        pkey_comp = crypto.derive_public_key(crypto.decodepoint(derivation), 0,
                                             crypto.decodepoint(base))
        self.assertEqual(pkey_ex, crypto.encodepoint(pkey_comp))
    def test_scalarmult(self):
        priv = unhexlify(
            b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
        )
        pub = unhexlify(
            b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0"
        )
        exp = unhexlify(
            b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d"
        )

        res = crypto.scalarmult(crypto.decodepoint(pub), crypto.decodeint(priv))
        self.assertEqual(exp, crypto.encodepoint(res))
        self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))
def _get_additional_public_key(
    src_entr: MoneroTransactionSourceEntry, ) -> Ge25519 | None:
    additional_tx_pub_key = None
    if len(src_entr.real_out_additional_tx_keys) == 1:  # compression
        additional_tx_pub_key = crypto.decodepoint(
            src_entr.real_out_additional_tx_keys[0])
    elif src_entr.real_out_additional_tx_keys:
        if src_entr.real_output_in_tx_index >= len(
                src_entr.real_out_additional_tx_keys):
            raise ValueError("Wrong number of additional derivations")
        additional_tx_pub_key = crypto.decodepoint(
            src_entr.real_out_additional_tx_keys[
                src_entr.real_output_in_tx_index])
    return additional_tx_pub_key
Example #9
0
def generate_mlsag_full(message, pubs, in_sk, out_sk_mask, out_pk_commitments,
                        kLRki, index, txn_fee_key):
    cols = len(pubs)
    if cols == 0:
        raise ValueError("Empty pubs")
    rows = len(pubs[0])
    if rows == 0:
        raise ValueError("Empty pub row")
    for i in range(cols):
        if len(pubs[i]) != rows:
            raise ValueError("pub is not rectangular")

    if len(in_sk) != rows:
        raise ValueError("Bad inSk size")
    if len(out_sk_mask) != len(out_pk_commitments):
        raise ValueError("Bad outsk/putpk size")

    sk = _key_vector(rows + 1)
    M = _key_matrix(rows + 1, cols)
    for i in range(rows + 1):
        sk[i] = crypto.sc_0()

    for i in range(cols):
        M[i][rows] = crypto.identity()
        for j in range(rows):
            M[i][j] = crypto.decodepoint(pubs[i][j].dest)
            M[i][rows] = crypto.point_add(
                M[i][rows], crypto.decodepoint(pubs[i][j].commitment))

    sk[rows] = crypto.sc_0()
    for j in range(rows):
        sk[j] = in_sk[j].dest
        sk[rows] = crypto.sc_add(sk[rows],
                                 in_sk[j].mask)  # add masks in last row

    for i in range(cols):
        for j in range(len(out_pk_commitments)):
            M[i][rows] = crypto.point_sub(
                M[i][rows], crypto.decodepoint(
                    out_pk_commitments[j]))  # subtract output Ci's in last row

        # Subtract txn fee output in last row
        M[i][rows] = crypto.point_sub(M[i][rows], txn_fee_key)

    for j in range(len(out_pk_commitments)):
        sk[rows] = crypto.sc_sub(
            sk[rows], out_sk_mask[j])  # subtract output masks in last row

    return generate_mlsag(message, M, sk, kLRki, index, rows)
Example #10
0
def export_key_image(creds, subaddresses, td):
    out_key = crypto.decodepoint(td.out_key)
    tx_pub_key = crypto.decodepoint(td.tx_pub_key)
    additional_tx_pub_keys = [
        crypto.decodepoint(x) for x in td.additional_tx_pub_keys
    ]
    ki, sig = _export_key_image(
        creds,
        subaddresses,
        out_key,
        tx_pub_key,
        additional_tx_pub_keys,
        td.internal_output_index,
    )
    return ki, sig
Example #11
0
    def verify_monero_generated(self, clsag):
        msg = ubinascii.unhexlify(clsag["msg"])
        sI = crypto.decodepoint(ubinascii.unhexlify(clsag["sI"]))
        sD = crypto.decodepoint(ubinascii.unhexlify(clsag["sD"]))
        sc1 = crypto.decodeint(ubinascii.unhexlify(clsag["sc1"]))
        Cout = crypto.decodepoint(ubinascii.unhexlify(clsag["cout"]))
        scalars = [
            crypto.decodeint(ubinascii.unhexlify(x)) for x in clsag["ss"]
        ]
        ring = []
        for e in clsag["ring"]:
            ring.append(
                TmpKey(ubinascii.unhexlify(e[0]), ubinascii.unhexlify(e[1])))

        self.verify_clsag(msg, scalars, sc1, sI, sD, ring, Cout)
Example #12
0
 def test_clsag_invalid_P(self):
     res = self.gen_clsag_sig(ring_size=11, index=5)
     msg, scalars, sc1, sI, sD, ring2, Cp = res
     with self.assertRaises(ValueError):
         ring2[5].commitment = crypto.encodepoint(
             crypto.point_mul8(crypto.decodepoint(ring2[5].dest)))
         self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
Example #13
0
def _compute_tx_keys(
        state: State, dst_entr: MoneroTransactionDestinationEntry
) -> Tuple[Ge25519, Sc25519]:
    """Computes tx_out_key, amount_key"""

    if state.is_processing_offloaded:
        return None, None  # no need to recompute

    # additional tx key if applicable
    additional_txkey_priv = _set_out_additional_keys(state, dst_entr)
    # derivation = a*R or r*A or s*C
    derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv)
    # amount key = H_s(derivation || i)
    amount_key = crypto.derivation_to_scalar(derivation,
                                             state.current_output_index)
    # one-time destination address P = H_s(derivation || i)*G + B
    tx_out_key = crypto.derive_public_key(
        derivation,
        state.current_output_index,
        crypto.decodepoint(dst_entr.addr.spend_public_key),
    )
    del (derivation, additional_txkey_priv)

    from apps.monero.xmr import monero

    mask = monero.commitment_mask(crypto.encodeint(amount_key))
    state.output_masks.append(mask)
    return tx_out_key, amount_key
Example #14
0
def _set_out_derivation(
    state: State,
    dst_entr: MoneroTransactionDestinationEntry,
    additional_txkey_priv: Sc25519,
) -> Ge25519:
    """
    Calculates derivation which is then used in the one-time address as
    `P = H(derivation)*G + B`.
    For change outputs the derivation equals a*R, because we know the
    private view key. For others it is either `r*A` for traditional
    addresses, or `s*C` for subaddresses. Both `r` and `s` are random
    scalars, `s` is used in the context of subaddresses, but it's
    basically the same thing.
    """
    from apps.monero.xmr.addresses import addr_eq

    change_addr = state.change_address()
    if change_addr and addr_eq(dst_entr.addr, change_addr):
        # sending change to yourself; derivation = a*R
        derivation = crypto.generate_key_derivation(
            state.tx_pub, state.creds.view_key_private)

    else:
        # sending to the recipient; derivation = r*A (or s*C in the subaddress scheme)
        if dst_entr.is_subaddress and state.need_additional_txkeys:
            deriv_priv = additional_txkey_priv
        else:
            deriv_priv = state.tx_priv
        derivation = crypto.generate_key_derivation(
            crypto.decodepoint(dst_entr.addr.view_public_key), deriv_priv)
    return derivation
Example #15
0
def _set_out_additional_keys(
    state: State, dst_entr: MoneroTransactionDestinationEntry
) -> Sc25519:
    """
    If needed (decided in step 1), additional tx keys are calculated
    for this particular output.
    """
    if not state.need_additional_txkeys:
        return None

    additional_txkey_priv = crypto.random_scalar()

    if dst_entr.is_subaddress:
        # R=r*D
        additional_txkey = crypto.decodepoint(dst_entr.addr.spend_public_key)
        crypto.scalarmult_into(
            additional_txkey, additional_txkey, additional_txkey_priv
        )
    else:
        # R=r*G
        additional_txkey = crypto.scalarmult_base(additional_txkey_priv)

    state.additional_tx_public_keys.append(crypto.encodepoint(additional_txkey))
    state.additional_tx_private_keys.append(additional_txkey_priv)
    return additional_txkey_priv
 def test_derive_subaddress_public_key(self):
     out_key = crypto.decodepoint(
         unhexlify(
             b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5"
         ))
     deriv = crypto.decodepoint(
         unhexlify(
             b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64"
         ))
     res = crypto.encodepoint(
         monero.derive_subaddress_public_key(out_key, deriv, 5))
     self.assertEqual(
         res,
         unhexlify(
             b"5a10cca900ee47a7f412cd661b29f5ab356d6a1951884593bb170b5ec8b6f2e8"
         ),
     )
    def test_derivation_to_scalar(self):
        derivation = unhexlify(
            b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01"
        )
        scalar = unhexlify(
            b"25d08763414c379aa9cf989cdcb3cadd36bd5193b500107d6bf5f921f18e470e"
        )

        sc_int = crypto.derivation_to_scalar(crypto.decodepoint(derivation), 0)
        self.assertEqual(scalar, crypto.encodeint(sc_int))
def _process_payment_id(state: State, tsx_data: MoneroTransactionData):
    """
    Writes payment id to the `extra` field under the TX_EXTRA_NONCE = 0x02 tag.

    The second tag describes if the payment id is encrypted or not.
    If the payment id is 8 bytes long it implies encryption and
    therefore the TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID = 0x01 tag is used.
    If it is not encrypted, we use TX_EXTRA_NONCE_PAYMENT_ID = 0x00.

    Since Monero release 0.13 all 2 output payments have encrypted payment ID
    to make BC more uniform.

    See:
    - https://github.com/monero-project/monero/blob/ff7dc087ae5f7de162131cea9dbcf8eac7c126a1/src/cryptonote_basic/tx_extra.h
    """
    # encrypted payment id / dummy payment ID
    view_key_pub_enc = None

    if not tsx_data.payment_id or len(tsx_data.payment_id) == 8:
        view_key_pub_enc = _get_key_for_payment_id_encryption(
            tsx_data, state.change_address(), state.client_version > 0)

    if not tsx_data.payment_id:
        return

    elif len(tsx_data.payment_id) == 8:
        view_key_pub = crypto.decodepoint(view_key_pub_enc)
        payment_id_encr = _encrypt_payment_id(tsx_data.payment_id,
                                              view_key_pub, state.tx_priv)

        extra_nonce = payment_id_encr
        extra_prefix = 1  # TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID

    # plain text payment id
    elif len(tsx_data.payment_id) == 32:
        extra_nonce = tsx_data.payment_id
        extra_prefix = 0  # TX_EXTRA_NONCE_PAYMENT_ID

    else:
        raise ValueError("Payment ID size invalid")

    lextra = len(extra_nonce)
    if lextra >= 255:
        raise ValueError("Nonce could be 255 bytes max")

    # write it to extra
    extra_buff = bytearray(3 + lextra)
    extra_buff[0] = 2  # TX_EXTRA_NONCE
    extra_buff[1] = lextra + 1
    extra_buff[2] = extra_prefix
    extra_buff[3:] = extra_nonce
    state.extra_nonce = extra_buff
Example #19
0
async def _refresh_step(s: LiveRefreshState, ctx,
                        msg: MoneroLiveRefreshStepRequest):
    buff = bytearray(32 * 3)
    buff_mv = memoryview(buff)

    await confirms.live_refresh_step(ctx, s.current_output)
    s.current_output += 1

    if __debug__:
        log.debug(__name__, "refresh, step i: %d", s.current_output)

    # Compute spending secret key and the key image
    # spend_priv = Hs(recv_deriv || real_out_idx) + spend_key_private
    # If subaddr:
    #   spend_priv += Hs("SubAddr" || view_key_private || major || minor)
    # out_key = spend_priv * G, KI: spend_priv * Hp(out_key)
    out_key = crypto.decodepoint(msg.out_key)
    recv_deriv = crypto.decodepoint(msg.recv_deriv)
    received_index = msg.sub_addr_major, msg.sub_addr_minor
    spend_priv, ki = monero.generate_tx_spend_and_key_image(
        s.creds, out_key, recv_deriv, msg.real_out_idx, received_index)

    ki_enc = crypto.encodepoint(ki)
    sig = key_image.generate_ring_signature(ki_enc, ki, [out_key], spend_priv,
                                            0, False)
    del spend_priv  # spend_priv never leaves the device

    # Serialize into buff
    buff[0:32] = ki_enc
    crypto.encodeint_into(buff_mv[32:64], sig[0][0])
    crypto.encodeint_into(buff_mv[64:], sig[0][1])

    # Encrypt with view key private based key - so host can decrypt and verify HMAC
    enc_key, salt = misc.compute_enc_key_host(s.creds.view_key_private,
                                              msg.out_key)
    resp = chacha_poly.encrypt_pack(enc_key, buff)

    return MoneroLiveRefreshStepAck(salt=salt, key_image=resp)
Example #20
0
async def get_tx_keys(ctx, msg: MoneroGetTxKeyRequest, keychain):
    await paths.validate_path(
        ctx, misc.validate_full_path, keychain, msg.address_n, CURVE
    )

    do_deriv = msg.reason == _GET_TX_KEY_REASON_TX_DERIVATION
    await confirms.require_confirm_tx_key(ctx, export_key=not do_deriv)

    creds = misc.get_creds(keychain, msg.address_n, msg.network_type)

    tx_enc_key = misc.compute_tx_key(
        creds.spend_key_private,
        msg.tx_prefix_hash,
        msg.salt1,
        crypto.decodeint(msg.salt2),
    )

    # the plain_buff first stores the tx_priv_keys as decrypted here
    # and then is used to store the derivations if applicable
    plain_buff = chacha_poly.decrypt_pack(tx_enc_key, msg.tx_enc_keys)
    utils.ensure(len(plain_buff) % 32 == 0, "Tx key buffer has invalid size")
    del msg.tx_enc_keys

    # If return only derivations do tx_priv * view_pub
    if do_deriv:
        plain_buff = bytearray(plain_buff)
        view_pub = crypto.decodepoint(msg.view_public_key)
        tx_priv = crypto.new_scalar()
        derivation = crypto.new_point()
        n_keys = len(plain_buff) // 32
        for c in range(n_keys):
            crypto.decodeint_into(tx_priv, plain_buff, 32 * c)
            crypto.scalarmult_into(derivation, view_pub, tx_priv)
            crypto.encodepoint_into(plain_buff, derivation, 32 * c)

    # Encrypt by view-key based password.
    tx_enc_key_host, salt = misc.compute_enc_key_host(
        creds.view_key_private, msg.tx_prefix_hash
    )

    res = chacha_poly.encrypt_pack(tx_enc_key_host, plain_buff)
    res_msg = MoneroGetTxKeyAck(salt=salt)
    if do_deriv:
        res_msg.tx_derivations = res
        return res_msg

    res_msg.tx_keys = res
    return res_msg
    def test_generate_key_derivation(self):
        key_pub = crypto.decodepoint(
            unhexlify(
                b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83"
            ))
        key_priv = crypto.decodeint(
            unhexlify(
                b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
            ))
        deriv_exp = unhexlify(
            b"fa188a45a0e4daccc0e6d4f6f6858fd46392104be74183ec0047e7e9f4eaf739"
        )

        self.assertEqual(
            deriv_exp,
            crypto.encodepoint(
                crypto.generate_key_derivation(key_pub, key_priv)),
        )
Example #22
0
def _check_subaddresses(state: State, outputs: list):
    """
    Using subaddresses leads to a few poorly documented exceptions.

    Normally we set R=r*G (tx_pub), however for subaddresses this is equal to R=r*D
    to achieve the nice blockchain scanning property.

    Remember, that R is per-transaction and not per-input. It's all good if we have a
    single output or we have a single destination and the second output is our change.
    This is because although the R=r*D, we can still derive the change using our private view-key.
    In other words, calculate the one-time address as P = H(x*R)*G + Y (where X,Y is the change).

    However, this does not work for other outputs than change, because we do not have the
    recipient's view key, so we cannot use the same formula -- we need a new R.

    The solution is very straightforward -- we create additional `R`s and use the `extra`
    field to include them under the `ADDITIONAL_PUBKEYS` tag.

    See:
    - https://lab.getmonero.org/pubs/MRL-0006.pdf
    - https://github.com/monero-project/monero/pull/2056
    """
    from apps.monero.xmr.addresses import classify_subaddresses

    # let's first figure out what kind of destinations we have
    num_stdaddresses, num_subaddresses, single_dest_subaddress = classify_subaddresses(
        outputs, state.change_address()
    )

    # if this is a single-destination transfer to a subaddress,
    # we set (override) the tx pubkey to R=r*D and no additional
    # tx keys are needed
    if num_stdaddresses == 0 and num_subaddresses == 1:
        state.tx_pub = crypto.scalarmult(
            crypto.decodepoint(single_dest_subaddress.spend_public_key), state.tx_priv
        )

    # if a subaddress is used and either standard address is as well
    # or more than one subaddress is used we need to add additional tx keys
    state.need_additional_txkeys = num_subaddresses > 0 and (
        num_stdaddresses > 0 or num_subaddresses > 1
    )
    state.mem_trace(4, True)
def _compute_tx_keys(state: State, dst_entr):
    """Computes tx_out_key, amount_key"""

    if state.is_processing_offloaded:
        return None, None  # no need to recompute

    # additional tx key if applicable
    additional_txkey_priv = _set_out_additional_keys(state, dst_entr)
    # derivation = a*R or r*A or s*C
    derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv)
    # amount key = H_s(derivation || i)
    amount_key = crypto.derivation_to_scalar(derivation,
                                             state.current_output_index)
    # one-time destination address P = H_s(derivation || i)*G + B
    tx_out_key = crypto.derive_public_key(
        derivation,
        state.current_output_index,
        crypto.decodepoint(dst_entr.addr.spend_public_key),
    )
    del (derivation, additional_txkey_priv)

    # Computes the newest mask if applicable
    if state.is_det_mask():
        from apps.monero.xmr import monero

        mask = monero.commitment_mask(crypto.encodeint(amount_key))

    elif state.current_output_index + 1 < state.output_count:
        mask = offloading_keys.det_comm_masks(state.key_enc,
                                              state.current_output_index)

    else:
        mask = state.output_last_mask
        state.output_last_mask = None

    state.output_masks.append(mask)
    return tx_out_key, amount_key
async def sign_input(
    state: State,
    src_entr: MoneroTransactionSourceEntry,
    vini_bin: bytes,
    vini_hmac: bytes,
    pseudo_out: bytes,
    pseudo_out_hmac: bytes,
    pseudo_out_alpha_enc: bytes,
    spend_enc: bytes,
    orig_idx: int,
) -> MoneroTransactionSignInputAck:
    """
    :param state: transaction state
    :param src_entr: Source entry
    :param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero)
    :param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor
    :param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha
                       as a mask. Only applicable for RCTTypeSimple.
    :param pseudo_out_hmac: HMAC for pseudo_out
    :param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted.
    :param spend_enc: one time address spending private key. Encrypted.
    :param orig_idx: original index of the src_entr before sorting (HMAC check)
    :return: Generated signature MGs[i]
    """
    await confirms.transaction_step(state, state.STEP_SIGN,
                                    state.current_input_index + 1)

    state.current_input_index += 1
    if state.last_step not in (state.STEP_ALL_OUT, state.STEP_SIGN):
        raise ValueError("Invalid state transition")
    if state.current_input_index >= state.input_count:
        raise ValueError("Invalid inputs count")
    if pseudo_out is None:
        raise ValueError("SimpleRCT requires pseudo_out but none provided")
    if pseudo_out_alpha_enc is None:
        raise ValueError(
            "SimpleRCT requires pseudo_out's mask but none provided")

    input_position = (state.source_permutation[state.current_input_index]
                      if state.client_version <= 1 else orig_idx)
    mods = utils.unimport_begin()

    # Check input's HMAC
    from apps.monero.signing import offloading_keys

    vini_hmac_comp = await offloading_keys.gen_hmac_vini(
        state.key_hmac, src_entr, vini_bin, input_position)
    if not crypto.ct_equals(vini_hmac_comp, vini_hmac):
        raise ValueError("HMAC is not correct")

    # Key image sorting check - permutation correctness
    cur_ki = offloading_keys.get_ki_from_vini(vini_bin)
    if state.current_input_index > 0 and state.last_ki <= cur_ki:
        raise ValueError("Key image order invalid")

    state.last_ki = cur_ki if state.current_input_index < state.input_count else None
    del (cur_ki, vini_bin, vini_hmac, vini_hmac_comp)

    gc.collect()
    state.mem_trace(1, True)

    from apps.monero.xmr.crypto import chacha_poly

    pseudo_out_alpha = crypto.decodeint(
        chacha_poly.decrypt_pack(
            offloading_keys.enc_key_txin_alpha(state.key_enc, input_position),
            bytes(pseudo_out_alpha_enc),
        ))

    # Last pseudo_out is recomputed so mask sums hold
    if input_position + 1 == state.input_count:
        # Recompute the lash alpha so the sum holds
        state.mem_trace("Correcting alpha")
        alpha_diff = crypto.sc_sub(state.sumout, state.sumpouts_alphas)
        crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff)
        pseudo_out_c = crypto.gen_commitment(pseudo_out_alpha,
                                             state.input_last_amount)

    else:
        if input_position + 1 == state.input_count:
            utils.ensure(crypto.sc_eq(state.sumpouts_alphas, state.sumout),
                         "Sum eq error")

        # both pseudo_out and its mask were offloaded so we need to
        # validate pseudo_out's HMAC and decrypt the alpha
        pseudo_out_hmac_comp = crypto.compute_hmac(
            offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position),
            pseudo_out,
        )
        if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac):
            raise ValueError("HMAC is not correct")

        pseudo_out_c = crypto.decodepoint(pseudo_out)

    state.mem_trace(2, True)

    # Spending secret
    spend_key = crypto.decodeint(
        chacha_poly.decrypt_pack(
            offloading_keys.enc_key_spend(state.key_enc, input_position),
            bytes(spend_enc),
        ))

    del (
        offloading_keys,
        chacha_poly,
        pseudo_out,
        pseudo_out_hmac,
        pseudo_out_alpha_enc,
        spend_enc,
    )
    utils.unimport_end(mods)
    state.mem_trace(3, True)

    # Basic setup, sanity check
    from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey

    index = src_entr.real_output
    input_secret_key = CtKey(spend_key, crypto.decodeint(src_entr.mask))

    # Private key correctness test
    utils.ensure(
        crypto.point_eq(
            crypto.decodepoint(
                src_entr.outputs[src_entr.real_output].key.dest),
            crypto.scalarmult_base(input_secret_key.dest),
        ),
        "Real source entry's destination does not equal spend key's",
    )
    utils.ensure(
        crypto.point_eq(
            crypto.decodepoint(
                src_entr.outputs[src_entr.real_output].key.commitment),
            crypto.gen_commitment(input_secret_key.mask, src_entr.amount),
        ),
        "Real source entry's mask does not equal spend key's",
    )

    state.mem_trace(4, True)

    from apps.monero.xmr import mlsag

    mg_buffer = []
    ring_pubkeys = [x.key for x in src_entr.outputs if x]
    utils.ensure(len(ring_pubkeys) == len(src_entr.outputs), "Invalid ring")
    del src_entr

    state.mem_trace(5, True)

    if state.hard_fork and state.hard_fork >= 13:
        state.mem_trace("CLSAG")
        mlsag.generate_clsag_simple(
            state.full_message,
            ring_pubkeys,
            input_secret_key,
            pseudo_out_alpha,
            pseudo_out_c,
            index,
            mg_buffer,
        )
    else:
        mlsag.generate_mlsag_simple(
            state.full_message,
            ring_pubkeys,
            input_secret_key,
            pseudo_out_alpha,
            pseudo_out_c,
            index,
            mg_buffer,
        )

    del (CtKey, input_secret_key, pseudo_out_alpha, mlsag, ring_pubkeys)
    state.mem_trace(6, True)

    from trezor.messages.MoneroTransactionSignInputAck import (
        MoneroTransactionSignInputAck, )

    # Encrypt signature, reveal once protocol finishes OK
    if state.client_version >= 3:
        utils.unimport_end(mods)
        state.mem_trace(7, True)
        mg_buffer = _protect_signature(state, mg_buffer)

    state.mem_trace(8, True)
    state.last_step = state.STEP_SIGN
    return MoneroTransactionSignInputAck(
        signature=mg_buffer, pseudo_out=crypto.encodepoint(pseudo_out_c))
async def set_input(
        state: State, src_entr: MoneroTransactionSourceEntry
) -> MoneroTransactionSetInputAck:
    from trezor.messages import MoneroTransactionSetInputAck
    from apps.monero.xmr.crypto import chacha_poly
    from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
    from apps.monero.signing import offloading_keys

    state.current_input_index += 1

    await layout.transaction_step(state, state.STEP_INP,
                                  state.current_input_index)

    if state.last_step > state.STEP_INP:
        raise ValueError("Invalid state transition")
    if state.current_input_index >= state.input_count:
        raise ValueError("Too many inputs")
    # real_output denotes which output in outputs is the real one (ours)
    if src_entr.real_output >= len(src_entr.outputs):
        raise ValueError(
            f"real_output index {src_entr.real_output} bigger than output_keys.size() {len(src_entr.outputs)}"
        )
    state.summary_inputs_money += src_entr.amount

    # Secrets derivation
    # the UTXO's one-time address P
    out_key = crypto.decodepoint(
        src_entr.outputs[src_entr.real_output].key.dest)
    # the tx_pub of our UTXO stored inside its transaction
    tx_key = crypto.decodepoint(src_entr.real_out_tx_key)
    additional_tx_pub_key = _get_additional_public_key(src_entr)

    # Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able
    # to spend the UTXO; and key image `I = x*H(P||i)`
    xi, ki, _di = monero.generate_tx_spend_and_key_image_and_derivation(
        state.creds,
        state.subaddresses,
        out_key,
        tx_key,
        additional_tx_pub_key,
        src_entr.real_output_in_tx_index,
        state.account_idx,
        src_entr.subaddr_minor,
    )
    state.mem_trace(1, True)

    # Construct tx.vin
    # If multisig is used then ki in vini should be src_entr.multisig_kLRki.ki
    vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki))
    vini.key_offsets = _absolute_output_offsets_to_relative(
        [x.idx for x in src_entr.outputs])

    if src_entr.rct:
        vini.amount = 0

    # Serialize `vini` with variant code for TxinToKey (prefix = TxinToKey.VARIANT_CODE).
    # The binary `vini_bin` is later sent to step 4 and 9 with its hmac,
    # where it is checked and directly used.
    vini_bin = serialize.dump_msg(vini, preallocate=64, prefix=b"\x02")
    state.mem_trace(2, True)

    # HMAC(T_in,i || vin_i)
    hmac_vini = offloading_keys.gen_hmac_vini(state.key_hmac, src_entr,
                                              vini_bin,
                                              state.current_input_index)
    state.mem_trace(3, True)

    # PseudoOuts commitment, alphas stored to state
    alpha, pseudo_out = _gen_commitment(state, src_entr.amount)
    pseudo_out = crypto.encodepoint(pseudo_out)

    # The alpha is encrypted and passed back for storage
    pseudo_out_hmac = crypto.compute_hmac(
        offloading_keys.hmac_key_txin_comm(state.key_hmac,
                                           state.current_input_index),
        pseudo_out,
    )

    alpha_enc = chacha_poly.encrypt_pack(
        offloading_keys.enc_key_txin_alpha(state.key_enc,
                                           state.current_input_index),
        crypto.encodeint(alpha),
    )

    spend_enc = chacha_poly.encrypt_pack(
        offloading_keys.enc_key_spend(state.key_enc,
                                      state.current_input_index),
        crypto.encodeint(xi),
    )

    state.last_step = state.STEP_INP
    if state.current_input_index + 1 == state.input_count:
        # When we finish the inputs processing, we no longer need
        # the precomputed subaddresses so we clear them to save memory.
        state.subaddresses = None
        state.input_last_amount = src_entr.amount

    return MoneroTransactionSetInputAck(
        vini=vini_bin,
        vini_hmac=hmac_vini,
        pseudo_out=pseudo_out,
        pseudo_out_hmac=pseudo_out_hmac,
        pseudo_out_alpha=alpha_enc,
        spend_key=spend_enc,
    )
async def sign_input(
    state: State,
    src_entr: MoneroTransactionSourceEntry,
    vini_bin: bytes,
    vini_hmac: bytes,
    pseudo_out: bytes,
    pseudo_out_hmac: bytes,
    pseudo_out_alpha_enc: bytes,
    spend_enc: bytes,
):
    """
    :param state: transaction state
    :param src_entr: Source entry
    :param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero)
    :param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor
    :param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha
                       as a mask. Only applicable for RCTTypeSimple.
    :param pseudo_out_hmac: HMAC for pseudo_out
    :param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted.
    :param spend_enc: one time address spending private key. Encrypted.
    :return: Generated signature MGs[i]
    """
    from apps.monero.signing import offloading_keys

    await confirms.transaction_step(state.ctx, state.STEP_SIGN,
                                    state.current_input_index + 1,
                                    state.input_count)

    state.current_input_index += 1
    if state.current_input_index >= state.input_count:
        raise ValueError("Invalid inputs count")
    if state.rct_type == RctType.Simple and pseudo_out is None:
        raise ValueError("SimpleRCT requires pseudo_out but none provided")
    if state.rct_type == RctType.Simple and pseudo_out_alpha_enc is None:
        raise ValueError(
            "SimpleRCT requires pseudo_out's mask but none provided")
    if state.current_input_index >= 1 and not state.rct_type == RctType.Simple:
        raise ValueError("Two and more inputs must imply SimpleRCT")

    input_position = state.source_permutation[state.current_input_index]

    # Check input's HMAC
    vini_hmac_comp = await offloading_keys.gen_hmac_vini(
        state.key_hmac, src_entr, vini_bin, input_position)
    if not crypto.ct_equals(vini_hmac_comp, vini_hmac):
        raise ValueError("HMAC is not correct")

    gc.collect()
    state.mem_trace(1)

    if state.rct_type == RctType.Simple:
        # both pseudo_out and its mask were offloaded so we need to
        # validate pseudo_out's HMAC and decrypt the alpha
        pseudo_out_hmac_comp = crypto.compute_hmac(
            offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position),
            pseudo_out,
        )
        if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac):
            raise ValueError("HMAC is not correct")

        gc.collect()
        state.mem_trace(2)

        from apps.monero.xmr.crypto import chacha_poly

        pseudo_out_alpha = crypto.decodeint(
            chacha_poly.decrypt_pack(
                offloading_keys.enc_key_txin_alpha(state.key_enc,
                                                   input_position),
                bytes(pseudo_out_alpha_enc),
            ))
        pseudo_out_c = crypto.decodepoint(pseudo_out)

    # Spending secret
    from apps.monero.xmr.crypto import chacha_poly
    from apps.monero.xmr.serialize_messages.ct_keys import CtKey

    spend_key = crypto.decodeint(
        chacha_poly.decrypt_pack(
            offloading_keys.enc_key_spend(state.key_enc, input_position),
            bytes(spend_enc),
        ))

    gc.collect()
    state.mem_trace(3)

    # Basic setup, sanity check
    index = src_entr.real_output
    input_secret_key = CtKey(dest=spend_key,
                             mask=crypto.decodeint(src_entr.mask))
    kLRki = None  # for multisig: src_entr.multisig_kLRki

    # Private key correctness test
    utils.ensure(
        crypto.point_eq(
            crypto.decodepoint(
                src_entr.outputs[src_entr.real_output].key.dest),
            crypto.scalarmult_base(input_secret_key.dest),
        ),
        "Real source entry's destination does not equal spend key's",
    )
    utils.ensure(
        crypto.point_eq(
            crypto.decodepoint(
                src_entr.outputs[src_entr.real_output].key.commitment),
            crypto.gen_commitment(input_secret_key.mask, src_entr.amount),
        ),
        "Real source entry's mask does not equal spend key's",
    )

    gc.collect()
    state.mem_trace(4)

    from apps.monero.xmr import mlsag

    if state.rct_type == RctType.Simple:
        ring_pubkeys = [x.key for x in src_entr.outputs]
        mg = mlsag.generate_mlsag_simple(
            state.full_message,
            ring_pubkeys,
            input_secret_key,
            pseudo_out_alpha,
            pseudo_out_c,
            kLRki,
            index,
        )

    else:
        # Full RingCt, only one input
        txn_fee_key = crypto.scalarmult_h(state.fee)
        ring_pubkeys = [[x.key] for x in src_entr.outputs]
        mg = mlsag.generate_mlsag_full(
            state.full_message,
            ring_pubkeys,
            [input_secret_key],
            state.output_sk_masks,
            state.output_pk_commitments,
            kLRki,
            index,
            txn_fee_key,
        )

    gc.collect()
    state.mem_trace(5)

    # Encode
    mgs = _recode_msg([mg])

    gc.collect()
    state.mem_trace(6)

    from trezor.messages.MoneroTransactionSignInputAck import (
        MoneroTransactionSignInputAck, )

    return MoneroTransactionSignInputAck(
        signature=serialize.dump_msg_gc(mgs[0], preallocate=488))
Example #27
0
def prove_range_mem(amount, last_mask=None):
    """
    Memory optimized range proof.

    Gives C, and mask such that \sumCi = C
    c.f. http:#eprint.iacr.org/2015/1098 section 5.1

    Ci is a commitment to either 0 or 2^i, i=0,...,63
    thus this proves that "amount" is in [0, 2^ATOMS]
    mask is a such that C = aG + bH, and b = amount
    :param amount:
    :param last_mask: ai[ATOMS-1] will be computed as \sum_{i=0}^{ATOMS-2} a_i - last_mask
    :param use_asnl: use ASNL, used before Borromean
    :return: sumCi, mask, RangeSig.
        sumCi is Pedersen commitment on the amount value. sumCi = aG + amount*H
        mask is "a" from the Pedersent commitment above.
    """
    res = bytearray(32 * (64 + 64 + 64 + 1))
    mv = memoryview(res)
    gc.collect()

    def as0(mv, x, i):
        crypto.encodeint_into(x, mv[32 * i:])

    def as1(mv, x, i):
        crypto.encodeint_into(x, mv[32 * 64 + 32 * i:])

    def aci(mv, x, i):
        crypto.encodepoint_into(x, mv[32 * 64 * 2 + 32 + 32 * i:])

    n = 64
    bb = d2b(amount, n)  # gives binary form of bb in "digits" binary digits
    ai = key_zero_vector(n)
    a = crypto.sc_0()

    C = crypto.identity()
    alpha = key_zero_vector(n)
    c_H = crypto.gen_H()
    kck = crypto.get_keccak()  # ee computation

    # First pass, generates: ai, alpha, Ci, ee, s1
    for ii in range(n):
        ai[ii] = crypto.random_scalar()
        if last_mask is not None and ii == 64 - 1:
            ai[ii] = crypto.sc_sub(last_mask, a)

        a = crypto.sc_add(
            a, ai[ii]
        )  # creating the total mask since you have to pass this to receiver...

        alpha[ii] = crypto.random_scalar()
        L = crypto.scalarmult_base(alpha[ii])

        if bb[ii] == 0:
            Ctmp = crypto.scalarmult_base(ai[ii])
        else:
            Ctmp = crypto.point_add(crypto.scalarmult_base(ai[ii]), c_H)
        C = crypto.point_add(C, Ctmp)
        aci(mv, Ctmp, ii)

        if bb[ii] == 0:
            si = crypto.random_scalar()
            c = crypto.hash_to_scalar(crypto.encodepoint(L))
            L = crypto.add_keys2(si, c, crypto.point_sub(Ctmp, c_H))
            kck.update(crypto.encodepoint(L))
            as1(mv, si, ii)

        else:
            kck.update(crypto.encodepoint(L))

        c_H = crypto.point_double(c_H)

    # Compute ee, memory cleanup
    ee = crypto.sc_reduce32(crypto.decodeint(kck.digest()))
    crypto.encodeint_into(ee, mv[64 * 32 * 2:])
    del kck
    gc.collect()

    # Second phase computes: s0, s1
    c_H = crypto.gen_H()

    for jj in range(n):
        if not bb[jj]:
            s0 = crypto.sc_mulsub(ai[jj], ee, alpha[jj])

        else:
            s0 = crypto.random_scalar()
            Ctmp = crypto.decodepoint(
                mv[32 * 64 * 2 + 32 + 32 * jj:32 * 64 * 2 + 32 + 32 * jj + 32])
            LL = crypto.add_keys2(s0, ee, Ctmp)
            cc = crypto.hash_to_scalar(crypto.encodepoint(LL))
            si = crypto.sc_mulsub(ai[jj], cc, alpha[jj])
            as1(mv, si, jj)
        as0(mv, s0, jj)

        c_H = crypto.point_double(c_H)

    gc.collect()
    return C, a, res
Example #28
0
async def sign_input(
    state: State,
    src_entr: MoneroTransactionSourceEntry,
    vini_bin: bytes,
    vini_hmac: bytes,
    pseudo_out: bytes,
    pseudo_out_hmac: bytes,
    pseudo_out_alpha_enc: bytes,
    spend_enc: bytes,
):
    """
    :param state: transaction state
    :param src_entr: Source entry
    :param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero)
    :param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor
    :param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha
                       as a mask. Only applicable for RCTTypeSimple.
    :param pseudo_out_hmac: HMAC for pseudo_out
    :param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted.
    :param spend_enc: one time address spending private key. Encrypted.
    :return: Generated signature MGs[i]
    """
    await confirms.transaction_step(state, state.STEP_SIGN,
                                    state.current_input_index + 1)

    state.current_input_index += 1
    if state.current_input_index >= state.input_count:
        raise ValueError("Invalid inputs count")
    if pseudo_out is None:
        raise ValueError("SimpleRCT requires pseudo_out but none provided")
    if pseudo_out_alpha_enc is None:
        raise ValueError(
            "SimpleRCT requires pseudo_out's mask but none provided")

    input_position = state.source_permutation[state.current_input_index]
    mods = utils.unimport_begin()

    # Check input's HMAC
    from apps.monero.signing import offloading_keys

    vini_hmac_comp = await offloading_keys.gen_hmac_vini(
        state.key_hmac, src_entr, vini_bin, input_position)
    if not crypto.ct_equals(vini_hmac_comp, vini_hmac):
        raise ValueError("HMAC is not correct")

    gc.collect()
    state.mem_trace(1, True)

    from apps.monero.xmr.crypto import chacha_poly

    pseudo_out_alpha = crypto.decodeint(
        chacha_poly.decrypt_pack(
            offloading_keys.enc_key_txin_alpha(state.key_enc, input_position),
            bytes(pseudo_out_alpha_enc),
        ))

    # Last pseud_out is recomputed so mask sums hold
    if state.is_det_mask() and input_position + 1 == state.input_count:
        # Recompute the lash alpha so the sum holds
        state.mem_trace("Correcting alpha")
        alpha_diff = crypto.sc_sub(state.sumout, state.sumpouts_alphas)
        crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff)
        pseudo_out_c = crypto.gen_commitment(pseudo_out_alpha,
                                             state.input_last_amount)

    else:
        if input_position + 1 == state.input_count:
            utils.ensure(crypto.sc_eq(state.sumpouts_alphas, state.sumout),
                         "Sum eq error")

        # both pseudo_out and its mask were offloaded so we need to
        # validate pseudo_out's HMAC and decrypt the alpha
        pseudo_out_hmac_comp = crypto.compute_hmac(
            offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position),
            pseudo_out,
        )
        if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac):
            raise ValueError("HMAC is not correct")

        pseudo_out_c = crypto.decodepoint(pseudo_out)

    state.mem_trace(2, True)

    # Spending secret
    spend_key = crypto.decodeint(
        chacha_poly.decrypt_pack(
            offloading_keys.enc_key_spend(state.key_enc, input_position),
            bytes(spend_enc),
        ))

    del (
        offloading_keys,
        chacha_poly,
        pseudo_out,
        pseudo_out_hmac,
        pseudo_out_alpha_enc,
        spend_enc,
    )
    utils.unimport_end(mods)
    state.mem_trace(3, True)

    from apps.monero.xmr.serialize_messages.ct_keys import CtKey

    # Basic setup, sanity check
    index = src_entr.real_output
    input_secret_key = CtKey(dest=spend_key,
                             mask=crypto.decodeint(src_entr.mask))
    kLRki = None  # for multisig: src_entr.multisig_kLRki

    # Private key correctness test
    utils.ensure(
        crypto.point_eq(
            crypto.decodepoint(
                src_entr.outputs[src_entr.real_output].key.dest),
            crypto.scalarmult_base(input_secret_key.dest),
        ),
        "Real source entry's destination does not equal spend key's",
    )
    utils.ensure(
        crypto.point_eq(
            crypto.decodepoint(
                src_entr.outputs[src_entr.real_output].key.commitment),
            crypto.gen_commitment(input_secret_key.mask, src_entr.amount),
        ),
        "Real source entry's mask does not equal spend key's",
    )

    state.mem_trace(4, True)

    from apps.monero.xmr import mlsag

    mg_buffer = []
    ring_pubkeys = [x.key for x in src_entr.outputs]
    del src_entr

    mlsag.generate_mlsag_simple(
        state.full_message,
        ring_pubkeys,
        input_secret_key,
        pseudo_out_alpha,
        pseudo_out_c,
        kLRki,
        index,
        mg_buffer,
    )

    del (input_secret_key, pseudo_out_alpha, mlsag, ring_pubkeys)
    state.mem_trace(5, True)

    from trezor.messages.MoneroTransactionSignInputAck import (
        MoneroTransactionSignInputAck, )

    return MoneroTransactionSignInputAck(
        signature=mg_buffer, pseudo_out=crypto.encodepoint(pseudo_out_c))
Example #29
0
async def set_output(state: State, dst_entr, dst_entr_hmac, rsig_data):
    state.mem_trace(0, True)
    mods = utils.unimport_begin()

    await confirms.transaction_step(state.ctx, state.STEP_OUT,
                                    state.current_output_index + 1,
                                    state.output_count)
    state.mem_trace(1)

    state.current_output_index += 1
    state.mem_trace(2, True)
    await _validate(state, dst_entr, dst_entr_hmac)

    # First output - we include the size of the container into the tx prefix hasher
    if state.current_output_index == 0:
        state.tx_prefix_hasher.uvarint(state.output_count)
    state.mem_trace(4, True)

    state.output_amounts.append(dst_entr.amount)
    state.summary_outs_money += dst_entr.amount
    utils.unimport_end(mods)
    state.mem_trace(5, True)

    # Range proof first, memory intensive
    rsig, mask = _range_proof(state, dst_entr.amount, rsig_data)
    utils.unimport_end(mods)
    state.mem_trace(6, True)

    # additional tx key if applicable
    additional_txkey_priv = _set_out_additional_keys(state, dst_entr)
    # derivation = a*R or r*A or s*C
    derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv)
    # amount key = H_s(derivation || i)
    amount_key = crypto.derivation_to_scalar(derivation,
                                             state.current_output_index)
    # one-time destination address P = H_s(derivation || i)*G + B
    tx_out_key = crypto.derive_public_key(
        derivation,
        state.current_output_index,
        crypto.decodepoint(dst_entr.addr.spend_public_key),
    )
    del (derivation, additional_txkey_priv)
    state.mem_trace(7, True)

    # Tx header prefix hashing, hmac dst_entr
    tx_out_bin, hmac_vouti = await _set_out_tx_out(state, dst_entr, tx_out_key)
    state.mem_trace(11, True)

    out_pk_dest, out_pk_commitment, ecdh_info_bin = _get_ecdh_info_and_out_pk(
        state=state,
        tx_out_key=tx_out_key,
        amount=dst_entr.amount,
        mask=mask,
        amount_key=amount_key,
    )
    del (dst_entr, mask, amount_key, tx_out_key)
    state.mem_trace(12, True)

    # Incremental hashing of the ECDH info.
    # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized
    # as whole vectors. We choose to hash ECDH first, because it saves state space.
    state.full_message_hasher.set_ecdh(ecdh_info_bin)
    state.mem_trace(13, True)

    # output_pk_commitment is stored to the state as it is used during the signature and hashed to the
    # RctSigBase later. No need to store amount, it was already stored.
    state.output_pk_commitments.append(out_pk_commitment)
    state.mem_trace(14, True)

    from trezor.messages.MoneroTransactionSetOutputAck import (
        MoneroTransactionSetOutputAck, )

    out_pk_bin = bytearray(64)
    utils.memcpy(out_pk_bin, 0, out_pk_dest, 0, 32)
    utils.memcpy(out_pk_bin, 32, out_pk_commitment, 0, 32)

    return MoneroTransactionSetOutputAck(
        tx_out=tx_out_bin,
        vouti_hmac=hmac_vouti,
        rsig_data=_return_rsig_data(rsig),
        out_pk=out_pk_bin,
        ecdh_info=ecdh_info_bin,
    )