def test_wallet_addr(self):
        addr = encode_addr(
            net_version(),
            unhexlify(
                b"3bec484c5d7f0246af520aab550452b5b6013733feabebd681c4a60d457b7fc1"
            ),
            unhexlify(
                b"2d5918e31d3c003da3c778592c07b398ad6f961a67082a75fd49394d51e69bbe"
            ),
        )

        self.assertEqual(
            addr,
            "43tpGG9PKbwCpjRvNLn1jwXPpnacw2uVUcszAtgmDiVcZK4VgHwjJT9BJz1WGF9eMxSYASp8yNMkuLjeQfWqJn3CNWdWfzV",
        )

        w = AccountCreds.new_wallet(
            crypto_helpers.decodeint(
                unhexlify(
                    b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08"
                )),
            crypto_helpers.decodeint(
                unhexlify(
                    b"f2644a3dd97d43e87887e74d1691d52baa0614206ad1b0c239ff4aa3b501750a"
                )),
            network_type=MoneroNetworkType.TESTNET,
        )
        self.assertEqual(
            w.address,
            "9vacMKaj8JJV6MnwDzh2oNVdwTLJfTDyNRiB6NzV9TT7fqvzLivH2dB8Tv7VYR3ncn8vCb3KdNMJzQWrPAF1otYJ9cPKpkr",
        )
Example #2
0
def generate_monero_keys(
    seed: bytes,
) -> tuple[crypto.Scalar, crypto.Point, crypto.Scalar, crypto.Point]:
    """
    Generates spend key / view key from the seed in the same manner as Monero code does.

    account.cpp:
    crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random).
    """
    spend_sec, spend_pub = generate_keys(crypto_helpers.decodeint(seed))
    hash = crypto.fast_hash_into(None, crypto_helpers.encodeint(spend_sec))
    view_sec, view_pub = generate_keys(crypto_helpers.decodeint(hash))
    return spend_sec, spend_pub, view_sec, view_pub
Example #3
0
    def verify_monero_generated(self, clsag):
        msg = ubinascii.unhexlify(clsag["msg"])
        sI = crypto_helpers.decodepoint(ubinascii.unhexlify(clsag["sI"]))
        sD = crypto_helpers.decodepoint(ubinascii.unhexlify(clsag["sD"]))
        sc1 = crypto_helpers.decodeint(ubinascii.unhexlify(clsag["sc1"]))
        Cout = crypto_helpers.decodepoint(ubinascii.unhexlify(clsag["cout"]))
        scalars = [
            crypto_helpers.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)
    def test_hash_to_scalar(self):
        inp = unhexlify(
            b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405"
        )

        res = crypto.hash_to_scalar_into(None, inp)
        exp = crypto_helpers.decodeint(
            unhexlify(
                b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06"
            ))
        self.assertTrue(crypto.sc_eq(res, exp))
 def test_get_subaddress_secret_key(self):
     a = crypto_helpers.decodeint(
         unhexlify(
             b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08"
         ))
     m = monero.get_subaddress_secret_key(secret_key=a, major=0, minor=1)
     self.assertEqual(
         crypto_helpers.encodeint(m),
         unhexlify(
             b"b6ff4d689b95e3310efbf683850c075bcde46361923054e42ef30016b287ff0c"
         ),
     )
    def test_sc_inversion(self):
        res = crypto.Scalar()
        inp = crypto_helpers.decodeint(
            unhexlify(
                b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
            ))

        crypto.sc_inv_into(res, inp)
        self.assertEqual(
            hexlify(crypto_helpers.encodeint(res)),
            b"bcf365a551e6358f3f281a6241d4a25eded60230b60a1d48c67b51a85e33d70e",
        )
Example #7
0
async def get_tx_keys(ctx: wire.Context, msg: MoneroGetTxKeyRequest,
                      keychain: Keychain) -> MoneroGetTxKeyAck:
    await paths.validate_path(ctx, keychain, msg.address_n)

    do_deriv = msg.reason == _GET_TX_KEY_REASON_TX_DERIVATION
    await layout.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_helpers.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")
    msg.tx_enc_keys = b""

    # If return only derivations do tx_priv * view_pub
    if do_deriv:
        if msg.view_public_key is None:
            raise wire.DataError("Missing view public key")

        plain_buff = bytearray(plain_buff)
        view_pub = crypto_helpers.decodepoint(msg.view_public_key)
        tx_priv = crypto.Scalar()
        derivation = crypto.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_scalarmult(self):
        priv = unhexlify(
            b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
        )
        pub = unhexlify(
            b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0"
        )
        exp = unhexlify(
            b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d"
        )

        res = crypto.scalarmult_into(None, crypto_helpers.decodepoint(pub),
                                     crypto_helpers.decodeint(priv))
        self.assertEqual(exp, crypto_helpers.encodepoint(res))
        self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))
    def test_scalarmult_base(self):
        scalar = crypto_helpers.decodeint(
            unhexlify(
                b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303"
            ))
        exp = unhexlify(
            b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09"
        )

        res = crypto.scalarmult_base_into(None, scalar)
        self.assertEqual(exp, crypto_helpers.encodepoint(res))
        self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))

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

        res = crypto.scalarmult_base_into(None, scalar)
        self.assertEqual(exp, crypto_helpers.encodepoint(res))
        self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))
    def test_generate_key_derivation(self):
        key_pub = crypto_helpers.decodepoint(
            unhexlify(
                b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83"
            ))
        key_priv = crypto_helpers.decodeint(
            unhexlify(
                b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
            ))
        deriv_exp = unhexlify(
            b"fa188a45a0e4daccc0e6d4f6f6858fd46392104be74183ec0047e7e9f4eaf739"
        )

        self.assertEqual(
            deriv_exp,
            crypto_helpers.encodepoint(
                crypto_helpers.generate_key_derivation(key_pub, key_priv)),
        )
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 layout.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 = orig_idx
    mods = utils.unimport_begin()

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

    vini_hmac_comp = 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 import chacha_poly

    pseudo_out_alpha = crypto_helpers.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_into(None, state.sumout,
                                        state.sumpouts_alphas)
        crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff)
        pseudo_out_c = crypto.gen_commitment_into(None, 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) != 0,
                "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_helpers.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_helpers.decodepoint(pseudo_out)

    state.mem_trace(2, True)

    # Spending secret
    spend_key = crypto_helpers.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_helpers.decodeint(src_entr.mask))

    # Private key correctness test
    utils.ensure(
        crypto.point_eq(
            crypto_helpers.decodepoint(
                src_entr.outputs[src_entr.real_output].key.dest),
            crypto.scalarmult_base_into(None, input_secret_key.dest),
        ),
        "Real source entry's destination does not equal spend key's",
    )
    utils.ensure(
        crypto.point_eq(
            crypto_helpers.decodepoint(
                src_entr.outputs[src_entr.real_output].key.commitment),
            crypto.gen_commitment_into(None, 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 clsag

    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)

    assert state.full_message is not None
    state.mem_trace("CLSAG")
    clsag.generate_clsag_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, clsag, ring_pubkeys)
    state.mem_trace(6, True)

    from trezor.messages import MoneroTransactionSignInputAck

    # Encrypt signature, reveal once protocol finishes OK
    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_helpers.encodepoint(pseudo_out_c))
Example #12
0
def det_comm_masks(key_enc: bytes, idx: int) -> crypto.Scalar:
    """
    Deterministic output commitment masks
    """
    return crypto_helpers.decodeint(_build_key(key_enc, b"out-mask", idx))
Example #13
0
    def gen_clsag_sig(self, ring_size=11, index=None):
        msg = random.bytes(32)
        amnt = crypto.Scalar(random.uniform(0xFFFFFF) + 12)
        priv = crypto.random_scalar()
        msk = crypto.random_scalar()
        alpha = crypto.random_scalar()
        P = crypto.scalarmult_base_into(None, priv)
        C = crypto.add_keys2_into(None, msk, amnt, crypto.xmr_H())
        Cp = crypto.add_keys2_into(None, alpha, amnt, crypto.xmr_H())

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

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

        self.assertTrue(
            crypto.point_eq(
                crypto.scalarmult_base_into(None, priv),
                crypto_helpers.decodepoint(ring[index].dest),
            ))
        self.assertTrue(
            crypto.point_eq(
                crypto.scalarmult_base_into(
                    None, crypto.sc_sub_into(None, msk, alpha)),
                crypto.point_sub_into(
                    None, crypto_helpers.decodepoint(ring[index].commitment),
                    Cp),
            ))

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

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

        crypto.hash_to_point_into(H, crypto_helpers.encodepoint(P))
        crypto.scalarmult_into(sI, H, priv)  # I = p*H
        return msg, scalars, sc1, sI, sD, ring2, Cp
Example #14
0
    def verify_clsag(self, msg, ss, sc1, sI, sD, pubs, C_offset):
        n = len(pubs)
        c = crypto.Scalar()
        D_8 = crypto.Point()
        tmp_bf = bytearray(32)
        C_offset_bf = crypto_helpers.encodepoint(C_offset)

        crypto.sc_copy(c, sc1)
        point_mul8_into(D_8, sD)

        hsh_P = crypto_helpers.get_keccak()  # domain, I, D, P, C, C_offset
        hsh_C = crypto_helpers.get_keccak()  # domain, I, D, P, C, C_offset
        hsh_P.update(clsag._HASH_KEY_CLSAG_AGG_0)
        hsh_C.update(clsag._HASH_KEY_CLSAG_AGG_1)

        def hsh_PC(x):
            hsh_P.update(x)
            hsh_C.update(x)

        for x in pubs:
            hsh_PC(x.dest)

        for x in pubs:
            hsh_PC(x.commitment)

        hsh_PC(crypto.encodepoint_into(tmp_bf, sI))
        hsh_PC(crypto.encodepoint_into(tmp_bf, sD))
        hsh_PC(C_offset_bf)
        mu_P = crypto_helpers.decodeint(hsh_P.digest())
        mu_C = crypto_helpers.decodeint(hsh_C.digest())

        c_to_hash = crypto_helpers.get_keccak(
        )  # domain, P, C, C_offset, message, L, R
        c_to_hash.update(clsag._HASH_KEY_CLSAG_ROUND)
        for i in range(len(pubs)):
            c_to_hash.update(pubs[i].dest)
        for i in range(len(pubs)):
            c_to_hash.update(pubs[i].commitment)
        c_to_hash.update(C_offset_bf)
        c_to_hash.update(msg)

        c_p = crypto.Scalar()
        c_c = crypto.Scalar()
        L = crypto.Point()
        R = crypto.Point()
        tmp_pt = crypto.Point()
        i = 0
        while i < n:
            crypto.sc_mul_into(c_p, mu_P, c)
            crypto.sc_mul_into(c_c, mu_C, c)

            C_P = crypto.point_sub_into(
                None, crypto.decodepoint_into(tmp_pt, pubs[i].commitment),
                C_offset)
            crypto.add_keys2_into(
                L, ss[i], c_p, crypto.decodepoint_into(tmp_pt, pubs[i].dest))
            crypto.point_add_into(L, L,
                                  crypto.scalarmult_into(tmp_pt, C_P, c_c))

            HP = crypto.hash_to_point_into(None, pubs[i].dest)
            crypto.add_keys3_into(R, ss[i], HP, c_p, sI)
            crypto.point_add_into(R, R,
                                  crypto.scalarmult_into(tmp_pt, D_8, c_c))

            chasher = c_to_hash.copy()
            chasher.update(crypto.encodepoint_into(tmp_bf, L))
            chasher.update(crypto.encodepoint_into(tmp_bf, R))
            crypto.decodeint_into(c, chasher.digest())
            i += 1
        res = crypto.sc_sub_into(None, c, sc1)
        if not crypto.sc_eq(res, crypto.Scalar(0)):
            raise ValueError("Signature error")
Example #15
0
def _generate_clsag(
    message: bytes,
    P: list[bytes],
    p: crypto.Scalar,
    C_nonzero: list[bytes],
    z: crypto.Scalar,
    Cout: crypto.Point,
    index: int,
    mg_buff: list[bytearray],
) -> list[bytes]:
    sI = crypto.Point()  # sig.I
    sD = crypto.Point()  # sig.D
    sc1 = crypto.Scalar()  # sig.c1
    a = crypto.random_scalar()
    H = crypto.Point()
    D = crypto.Point()
    Cout_bf = crypto_helpers.encodepoint(Cout)

    tmp_sc = crypto.Scalar()
    tmp = crypto.Point()
    tmp_bf = bytearray(32)

    crypto.hash_to_point_into(H, P[index])
    crypto.scalarmult_into(sI, H, p)  # I = p*H
    crypto.scalarmult_into(D, H, z)  # D = z*H
    crypto.sc_mul_into(tmp_sc, z, crypto_helpers.INV_EIGHT_SC)  # 1/8*z
    crypto.scalarmult_into(sD, H, tmp_sc)  # sig.D = 1/8*z*H
    sD = crypto_helpers.encodepoint(sD)

    hsh_P = crypto_helpers.get_keccak()  # domain, I, D, P, C, C_offset
    hsh_C = crypto_helpers.get_keccak()  # domain, I, D, P, C, C_offset
    hsh_P.update(_HASH_KEY_CLSAG_AGG_0)
    hsh_C.update(_HASH_KEY_CLSAG_AGG_1)

    def hsh_PC(x):
        nonlocal hsh_P, hsh_C
        hsh_P.update(x)
        hsh_C.update(x)

    for x in P:
        hsh_PC(x)

    for x in C_nonzero:
        hsh_PC(x)

    hsh_PC(crypto.encodepoint_into(tmp_bf, sI))
    hsh_PC(sD)
    hsh_PC(Cout_bf)
    mu_P = crypto_helpers.decodeint(hsh_P.digest())
    mu_C = crypto_helpers.decodeint(hsh_C.digest())

    del (hsh_PC, hsh_P, hsh_C)
    c_to_hash = crypto_helpers.get_keccak()  # domain, P, C, C_offset, message, aG, aH
    c_to_hash.update(_HASH_KEY_CLSAG_ROUND)
    for i in range(len(P)):
        c_to_hash.update(P[i])
    for i in range(len(P)):
        c_to_hash.update(C_nonzero[i])
    c_to_hash.update(Cout_bf)
    c_to_hash.update(message)

    chasher = c_to_hash.copy()
    crypto.scalarmult_base_into(tmp, a)
    chasher.update(crypto.encodepoint_into(tmp_bf, tmp))  # aG
    crypto.scalarmult_into(tmp, H, a)
    chasher.update(crypto.encodepoint_into(tmp_bf, tmp))  # aH
    c = crypto_helpers.decodeint(chasher.digest())
    del (chasher, H)

    L = crypto.Point()
    R = crypto.Point()
    c_p = crypto.Scalar()
    c_c = crypto.Scalar()
    i = (index + 1) % len(P)
    if i == 0:
        crypto.sc_copy(sc1, c)

    mg_buff.append(int_serialize.dump_uvarint_b(len(P)))
    for _ in range(len(P)):
        mg_buff.append(bytearray(32))

    while i != index:
        crypto.random_scalar(tmp_sc)
        crypto.encodeint_into(mg_buff[i + 1], tmp_sc)

        crypto.sc_mul_into(c_p, mu_P, c)
        crypto.sc_mul_into(c_c, mu_C, c)

        # L = tmp_sc * G + c_P * P[i] + c_c * C[i]
        crypto.add_keys2_into(L, tmp_sc, c_p, crypto.decodepoint_into(tmp, P[i]))
        crypto.decodepoint_into(tmp, C_nonzero[i])  # C = C_nonzero - Cout
        crypto.point_sub_into(tmp, tmp, Cout)
        crypto.scalarmult_into(tmp, tmp, c_c)
        crypto.point_add_into(L, L, tmp)

        # R = tmp_sc * HP + c_p * I + c_c * D
        crypto.hash_to_point_into(tmp, P[i])
        crypto.add_keys3_into(R, tmp_sc, tmp, c_p, sI)
        crypto.point_add_into(R, R, crypto.scalarmult_into(tmp, D, c_c))

        chasher = c_to_hash.copy()
        chasher.update(crypto.encodepoint_into(tmp_bf, L))
        chasher.update(crypto.encodepoint_into(tmp_bf, R))
        crypto.decodeint_into(c, chasher.digest())

        P[i] = None  # type: ignore
        C_nonzero[i] = None  # type: ignore

        i = (i + 1) % len(P)
        if i == 0:
            crypto.sc_copy(sc1, c)

        if i & 3 == 0:
            gc.collect()

    # Final scalar = a - c * (mu_P * p + mu_c * Z)
    crypto.sc_mul_into(tmp_sc, mu_P, p)
    crypto.sc_muladd_into(tmp_sc, mu_C, z, tmp_sc)
    crypto.sc_mulsub_into(tmp_sc, c, tmp_sc, a)
    crypto.encodeint_into(mg_buff[index + 1], tmp_sc)

    if TYPE_CHECKING:
        assert list_of_type(mg_buff, bytes)

    mg_buff.append(crypto_helpers.encodeint(sc1))
    mg_buff.append(sD)
    return mg_buff