diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 77bc05c..57fe17b 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -91,6 +91,16 @@ namespace crypto { return &reinterpret_cast(scalar); } + static const mx25519_impl* get_mx25519_impl() + { + static std::once_flag of; + static const mx25519_impl *impl; + std::call_once(of, [&](){ impl = mx25519_select_impl(MX25519_TYPE_AUTO); }); + if (impl == nullptr) + throw std::runtime_error("failed to obtain a mx25519 implementation"); + return impl; + } + boost::mutex &get_random_lock() { static boost::mutex random_lock; @@ -512,7 +522,194 @@ namespace crypto { const boost::optional &B, // Ed if present const public_key &D, // X25519 u-coordinate const secret_key &r, + const secret_key &a, signature &sig) + { + // Check if we are sender or receiver + if (r != crypto::null_skey) { + // SENDER + generate_carrot_tx_proof_as_sender(prefix_hash, R, A, B, D, r, a, sig); + return; + } + + // RECEIVER + + // Load points (A and B and R) into ge_p3 + ge_p3 A_p3; + ge_p3 B_p3; + ge_p3 R_p3; + + if (ge_frombytes_vartime(&A_p3, &A) != 0) + throw std::runtime_error("recipient view pubkey is invalid"); + + if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) + throw std::runtime_error("recipient spend pubkey is invalid"); + + if (ge_frombytes_vartime(&R_p3, &R) != 0) + throw std::runtime_error("tx pubkey is invalid"); + +#if !defined(NDEBUG) + { + // Debug check D == a*R + mx25519_pubkey D_x25519; + mx25519_scmul_key(get_mx25519_impl(), + &D_x25519, + reinterpret_cast(&a), + reinterpret_cast(&R)); + public_key dbg_D; + memcpy(&dbg_D, &D_x25519, sizeof(mx25519_pubkey)); + assert(D == dbg_D); + } +#endif + + // + // 1. Pick random nonce k + // + crypto::secret_key k; + random_scalar(k); + + static const public_key zero = {{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }}; + + s_comm_2 buf; + buf.msg = prefix_hash; + buf.D = D; // X25519 u-coord + buf.R = R; // X25519 u-coord + buf.A = A; // Ed25519 + buf.B = B ? *B : zero; + + cn_fast_hash(config::HASH_KEY_TXPROOF_V2, + sizeof(config::HASH_KEY_TXPROOF_V2)-1, + buf.sep); + + // + // 2. Compute X = ConvertPointE(k*G or k*B) + // + ge_p3 kB_or_kG_p3; + if (B) + ge_scalarmult_p3(&kB_or_kG_p3, &k, &B_p3); + else + ge_scalarmult_base(&kB_or_kG_p3, &k); + mx25519_pubkey X_x25519; + ge_p3_to_x25519(X_x25519.data, &kB_or_kG_p3); + memcpy(&buf.X, &X_x25519, sizeof(mx25519_pubkey)); + + // + // 3. Compute Y = k*R + // + mx25519_pubkey Y; + mx25519_scmul_key(get_mx25519_impl(), + &Y, + reinterpret_cast(&k), + reinterpret_cast(&R)); + memcpy(&buf.Y, &Y, sizeof(mx25519_pubkey)); + + // ---------- Extract and lift R ---------- + fe u_R; + fe_frombytes_vartime(u_R, (const unsigned char *)&R); + + fe v_R_cand; + if (fe_sqrt_mont(v_R_cand, u_R) != 0) + throw std::runtime_error("R not on curve"); + + fe x1, y1, x2, y2, v_R_neg; + ge_p3 R_ed1, R_ed2; + + // +v (principal) + mont_to_ed(x1, y1, u_R, v_R_cand); + ge_from_xy(&R_ed1, x1, y1); + + // -v + fe_neg(v_R_neg, v_R_cand); + mont_to_ed(x2, y2, u_R, v_R_neg); + ge_from_xy(&R_ed2, x2, y2); + + // Arbitrarily choose R_sign = true (principal v from fe_sqrt_mont) + bool R_sign = true; + ge_p3 R_ed_correct = R_ed1; // +v + + // ---------- Extract and lift D (consistent with chosen R_sign) ---------- + fe u_D; + fe_frombytes_vartime(u_D, (const unsigned char *)&D); + + fe v_D_cand; + if (fe_sqrt_mont(v_D_cand, u_D) != 0) + throw std::runtime_error("D not on curve"); + + fe x3, y3, x4, y4, v_D_neg; + + // Compute D_ed_true = a * R_ed_correct + ge_p3 D_ed_true; + ge_scalarmult_p3(&D_ed_true, &a, &R_ed_correct); + + // Normalize to affine for matching + fe inv_z; + fe_invert(inv_z, D_ed_true.Z); + fe xd_true, yd_true; + fe_mul(xd_true, D_ed_true.X, inv_z); + fe_mul(yd_true, D_ed_true.Y, inv_z); + + // +v for D + mont_to_ed(x3, y3, u_D, v_D_cand); + bool D_match1 = fe_equal(x3, xd_true) && fe_equal(y3, yd_true); // Affine match (mont_to_ed gives affine x,y) + + // -v for D + fe_neg(v_D_neg, v_D_cand); + mont_to_ed(x4, y4, u_D, v_D_neg); + bool D_match2 = fe_equal(x4, xd_true) && fe_equal(y4, yd_true); + + bool D_sign = false; + if (D_match1) + D_sign = true; + else if (D_match2) + D_sign = false; + else + throw std::runtime_error("D lift mismatch with computed D_ed_true"); + + // Pack signs (MSB is set to [1] for outbound, [0] for inbound + sig.sign_mask = + (R_sign ? 0x01 : 0x00) | + (D_sign ? 0x02 : 0x00); + + struct { + s_comm_2 buf; + uint8_t sign_mask; + } challenge_hash; + + challenge_hash.buf = buf; + challenge_hash.sign_mask = sig.sign_mask; + + // + // 7. Compute challenge c = H(prefix_hash || … || sign_mask) + // + hash_to_scalar(&challenge_hash, sizeof(challenge_hash), sig.c); + + // + // 8. Compute response z = k - c*a + // + sc_mulsub(&sig.r, &sig.c, &unwrap(a), &k); + + memwipe(&k, sizeof(k)); + +#if !defined(NDEBUG) + bool ok = check_carrot_tx_proof(prefix_hash, R, A, B, D, sig); + assert(ok); +#endif + } + + void crypto_ops::generate_carrot_tx_proof_as_sender( + const hash &prefix_hash, + const public_key &R, // X25519 u-coordinate + const public_key &A, // Ed25519 + const boost::optional &B, // Ed if present + const public_key &D, // X25519 u-coordinate + const secret_key &r, + const secret_key &a, + signature &sig) { // Load only Ed points (A and B) into ge_p3 ge_p3 A_p3; @@ -542,15 +739,15 @@ namespace crypto { memcpy(&dbg_R, &R_x25519, sizeof(mx25519_pubkey)); assert(R == dbg_R); - + // Debug check D == ConvertPointE(r*A) public_key dbg_D; ge_p3 dbg_D_p3; ge_scalarmult_p3(&dbg_D_p3, &r, &A_p3); - + mx25519_pubkey D_x25519; ge_p3_to_x25519(D_x25519.data, &dbg_D_p3); - + memcpy(&dbg_D, &D_x25519, sizeof(mx25519_pubkey)); assert(D == dbg_D); } @@ -677,7 +874,8 @@ namespace crypto { // sig.sign_mask = (R_sign ? 0x01 : 0x00) | - (D_sign ? 0x02 : 0x00); + (D_sign ? 0x02 : 0x00) | + 0x80; struct { s_comm_2 buf; @@ -827,9 +1025,10 @@ namespace crypto { if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false; - // Extract sign bits - const bool R_sign = (sig.sign_mask & 0x01) != 0; - const bool D_sign = (sig.sign_mask & 0x02) != 0; + // Extract sign bits and direction flag + const bool R_sign = (sig.sign_mask & 0x01) != 0; + const bool D_sign = (sig.sign_mask & 0x02) != 0; + const bool outbound = (sig.sign_mask & 0x80) != 0; // // 1. Reconstruct R_ed and D_ed from X25519 u-coords + sign bits @@ -862,31 +1061,36 @@ namespace crypto { ge_from_xy(&D_ed, x_D, y_D); // - // 2. Compute X' = z*G + c*R_ed (or z*B + c*R_ed) in Edwards + // 2. Compute X' + // If inbound proof, X`= z*G + c*A (or z*B + c*A) + // If outbound proof, X`= z*G + c*R_ed (or z*B + c*R_ed) // - ge_p3 cR_p3; - ge_scalarmult_p3(&cR_p3, &sig.c, &R_ed); + ge_p3 c_p3; + if (outbound) + ge_scalarmult_p3(&c_p3, &sig.c, &R_ed); + else + ge_scalarmult_p3(&c_p3, &sig.c, &A_p3); ge_p1p1 X_p1p1; if (B) - { - // Subaddress: X' = c*R_ed + z*B - ge_p3 rB_p3; - ge_scalarmult_p3(&rB_p3, &sig.r, &B_p3); - ge_cached rB_cached; + { + // Subaddress: X' = c*A + z*B + ge_p3 rB_p3; + ge_scalarmult_p3(&rB_p3, &sig.r, &B_p3); + ge_cached rB_cached; ge_p3_to_cached(&rB_cached, &rB_p3); - ge_add(&X_p1p1, &cR_p3, &rB_cached); - } + ge_add(&X_p1p1, &c_p3, &rB_cached); + } else - { - // Main address: X' = c*R_ed + z*G - ge_p3 rG_p3; - ge_scalarmult_base(&rG_p3, &sig.r); - ge_cached rG_cached; - ge_p3_to_cached(&rG_cached, &rG_p3); - ge_add(&X_p1p1, &cR_p3, &rG_cached); - } + { + // Main address: X' = c*R_ed + z*G + ge_p3 rG_p3; + ge_scalarmult_base(&rG_p3, &sig.r); + ge_cached rG_cached; + ge_p3_to_cached(&rG_cached, &rG_p3); + ge_add(&X_p1p1, &c_p3, &rG_cached); + } ge_p3 X_ed_p3; ge_p1p1_to_p3(&X_ed_p3, &X_p1p1); @@ -895,20 +1099,25 @@ namespace crypto { ge_p3_to_x25519(X_x25519.data, &X_ed_p3); // - // 3. Compute Y' = c*D_ed + z*A in Edwards + // 3. Compute Y' + // If inbound, Y' = c*D_ed + z*R + // If outbound, Y' = c*D_ed + z*A // ge_p3 cD_p3; ge_scalarmult_p3(&cD_p3, &sig.c, &D_ed); - ge_p3 rA_p3; - ge_scalarmult_p3(&rA_p3, &sig.r, &A_p3); + ge_p3 z_p3; + if (outbound) + ge_scalarmult_p3(&z_p3, &sig.r, &A_p3); + else + ge_scalarmult_p3(&z_p3, &sig.r, &R_ed); - ge_cached rA_cached; - ge_p3_to_cached(&rA_cached, &rA_p3); + ge_cached z_cached; + ge_p3_to_cached(&z_cached, &z_p3); ge_p1p1 Y_p1p1; - ge_add(&Y_p1p1, &cD_p3, &rA_cached); + ge_add(&Y_p1p1, &cD_p3, &z_cached); ge_p3 Y_ed_p3; ge_p1p1_to_p3(&Y_ed_p3, &Y_p1p1); diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index f45058e..b1de0d1 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -132,8 +132,10 @@ namespace crypto { friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); static void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); friend void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); - static void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); - friend void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); + static void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, const secret_key &, signature &); + friend void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, const secret_key &, signature &); + static void generate_carrot_tx_proof_as_sender(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, const secret_key &, signature &); + friend void generate_carrot_tx_proof_as_sender(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, const secret_key &, signature &); static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &, const int); friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &, const int); static bool check_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &); @@ -268,8 +270,11 @@ namespace crypto { /* Generation of a carrot tx proof; for carrot transactions, D is in X25519 domain (D = r*ConvertPointE(A)) * instead of Ed25519 domain (D = r*A). This version applies ConvertPointE transformation. */ - inline void generate_carrot_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const secret_key &r, signature &sig) { - crypto_ops::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, sig); + inline void generate_carrot_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const secret_key &r, const secret_key &a, signature &sig) { + crypto_ops::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, a, sig); + } + inline void generate_carrot_tx_proof_as_sender(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const secret_key &r, const secret_key &a, signature &sig) { + crypto_ops::generate_carrot_tx_proof_as_sender(prefix_hash, R, A, B, D, r, a, sig); } inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const signature &sig, const int version) { return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig, version); @@ -279,7 +284,11 @@ namespace crypto { inline bool check_carrot_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const signature &sig) { return crypto_ops::check_carrot_tx_proof(prefix_hash, R, A, B, D, sig); } - + /* + inline bool check_carrot_tx_proof_as_sender(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const signature &sig) { + return crypto_ops::check_carrot_tx_proof_as_sender(prefix_hash, R, A, B, D, sig); + } + */ inline void derive_key_image_generator(const public_key &pub, ec_point &ki_gen) { crypto_ops::derive_key_image_generator(pub, ki_gen); } diff --git a/src/device/device.hpp b/src/device/device.hpp index 30f0d74..e96f2e1 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -203,8 +203,8 @@ namespace hw { crypto::signature &sig) = 0; virtual void generate_carrot_tx_proof(const crypto::hash &prefix_hash, - const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, - crypto::signature &sig) = 0; + const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, + const crypto::secret_key &a, crypto::signature &sig) = 0; virtual bool open_tx(crypto::secret_key &tx_key) = 0; diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index 10c8914..abd5dae 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -284,8 +284,8 @@ namespace hw { void device_default::generate_carrot_tx_proof(const crypto::hash &prefix_hash, const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, - crypto::signature &sig) { - crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, sig); + const crypto::secret_key &a, crypto::signature &sig) { + crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, a, sig); } bool device_default::open_tx(crypto::secret_key &tx_key) { diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index d240f86..182c4ee 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -113,8 +113,8 @@ namespace hw { crypto::signature &sig) override; void generate_carrot_tx_proof(const crypto::hash &prefix_hash, - const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, - crypto::signature &sig) override; + const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, + const crypto::secret_key &a, crypto::signature &sig) override; bool open_tx(crypto::secret_key &tx_key) override; void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) override; diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 58f15d7..865f55e 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -1434,11 +1434,11 @@ namespace hw { } void device_ledger::generate_carrot_tx_proof(const crypto::hash &prefix_hash, - const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, - crypto::signature &sig) { + const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, + const crypto::secret_key &a, crypto::signature &sig) { // to-do: For now, carrot tx proofs are not supported AUTO_LOCK_CMD(); - crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, sig); + crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, a, sig); } bool device_ledger::open_tx(crypto::secret_key &tx_key) { diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index df8e2dd..6e7a5e3 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -254,8 +254,8 @@ namespace hw { crypto::signature &sig) override; void generate_carrot_tx_proof(const crypto::hash &prefix_hash, - const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, - crypto::signature &sig) override; + const crypto::public_key &R, const crypto::public_key &A, const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, + const crypto::secret_key &a, crypto::signature &sig) override; bool open_tx(crypto::secret_key &tx_key) override; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 74b2f32..0093df4 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13222,13 +13222,18 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt const boost::optional &B, const crypto::public_key &D, const crypto::secret_key &r, + const crypto::secret_key &a, crypto::signature &sig) { if (address.m_is_carrot) { - hwdev.generate_carrot_tx_proof(prefix_hash, R, A, is_subaddress ? B : boost::none, D, r, sig); + hwdev.generate_carrot_tx_proof(prefix_hash, R, A, is_subaddress ? B : boost::none, D, r, a, sig); } else { crypto::signature sig_cn; - hwdev.generate_tx_proof(prefix_hash, R, A, is_subaddress ? B : boost::none, D, r, sig_cn); + if (r != crypto::null_skey) { + hwdev.generate_tx_proof(prefix_hash, R, A, is_subaddress ? B : boost::none, D, r, sig_cn); + } else { + hwdev.generate_tx_proof(prefix_hash, A, R, is_subaddress ? B : boost::none, D, a, sig_cn); + } sig.c = sig_cn.c; sig.r = sig_cn.r; sig.sign_mask = 0xFF; @@ -13307,11 +13312,6 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt // determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound) const bool is_out = m_account.get_subaddress_map_ref().count(address.m_spend_public_key) == 0; - // determine if tx_key is invalid and should be skipped - const bool skip_txkey = (tx_key == crypto::null_skey && - address.m_is_carrot && - tx.vout.size() == additional_tx_keys.size()); - const crypto::hash txid = cryptonote::get_transaction_hash(tx); std::string prefix_data((const char*)&txid, sizeof(crypto::hash)); prefix_data += message; @@ -13324,6 +13324,11 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt if (is_out) { + // determine if tx_key is invalid and should be skipped + const bool skip_txkey = (tx_key == crypto::null_skey && + address.m_is_carrot && + tx.vout.size() == additional_tx_keys.size()); + const size_t num_sigs = 1 + additional_tx_keys.size(); shared_secret.resize(num_sigs); sig.resize(num_sigs); @@ -13341,7 +13346,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt shared_secret[0]); calculate_tx_public_key_fn(tx_key, tx_pub_key); - generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]); + generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, crypto::null_skey, sig[0]); } for (size_t i = 1; i < num_sigs; ++i) { @@ -13361,16 +13366,23 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt shared_secret[i]); calculate_tx_public_key_fn(additional_tx_keys[i - 1], tx_pub_key); - generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], crypto::null_skey, sig[i]); } sig_str = address.m_is_carrot ? std::string("OutProofV3") : std::string("OutProofV2"); } else { crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); - std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + + // determine if tx_pub_key is invalid and should be skipped + const bool skip_tx_pubkey = (tx_pub_key == crypto::null_pkey && + address.m_is_carrot && + tx.vout.size() == additional_tx_pub_keys.size()); + + if (!skip_tx_pubkey) + THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); + const size_t num_sigs = 1 + additional_tx_pub_keys.size(); shared_secret.resize(num_sigs); sig.resize(num_sigs); @@ -13379,7 +13391,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt crypto::public_key dummy_pkey; crypto::secret_key dummy_skey; - if (!skip_txkey) { + if (!skip_tx_pubkey) { calculate_shared_secret_fn(dummy_pkey, a, tx_pub_key, @@ -13387,11 +13399,15 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt is_out, shared_secret[0]); - generate_proof_fn(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]); + generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], crypto::null_skey, a, sig[0]); } for (size_t i = 1; i < num_sigs; ++i) { + // Is this an invalid key? + if (skip_tx_pubkey && additional_tx_pub_keys[i - 1] == crypto::null_pkey) + continue; + calculate_shared_secret_fn(dummy_pkey, a, additional_tx_pub_keys[i - 1], @@ -13399,7 +13415,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt is_out, shared_secret[i]); - generate_proof_fn(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]); + generate_proof_fn(prefix_hash, additional_tx_pub_keys[i - 1], address.m_view_public_key, address.m_spend_public_key, shared_secret[i], crypto::null_skey, a, sig[i]); } sig_str = address.m_is_carrot ? std::string("InProofV3") : std::string("InProofV2"); } @@ -13606,7 +13622,19 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote { if (version == 3) { - //to-do + if (tx_pub_key != crypto::null_pkey) { + good_signature[0] = is_subaddress ? + crypto::check_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) : + crypto::check_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]); + } + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + if (additional_tx_pub_keys[i] != crypto::null_pkey) { + good_signature[i + 1] = is_subaddress ? + crypto::check_carrot_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : + crypto::check_carrot_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]); + } + } } else { diff --git a/tests/unit_tests/carrot_tx_proof.cpp b/tests/unit_tests/carrot_tx_proof.cpp index 93ed266..eba26c8 100644 --- a/tests/unit_tests/carrot_tx_proof.cpp +++ b/tests/unit_tests/carrot_tx_proof.cpp @@ -123,7 +123,7 @@ TEST(carrot_tx_proofs, fuzz_stability) prefix_hash, R_pk, A, use_subaddress ? boost::make_optional(B) : boost::none, - D_pk, r, sig + D_pk, r, a, sig ); // 8. Verify @@ -274,7 +274,7 @@ TEST(carrot_tx_proofs, known_values_mutation_rejection_main_address) // Generate proof with known values crypto::signature sig; - crypto::generate_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, r, sig); + crypto::generate_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, r, a, sig); // Verify original proof works ASSERT_TRUE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, sig)); @@ -347,7 +347,7 @@ TEST(carrot_tx_proofs, known_values_mutation_rejection_subaddress) // Tx ID: 01f5e1e56df714e3af919ab443b1acc4b1bebffed03198a9aaf3d22449809453 // Tx priv key - crypto::secret_key r; + crypto::secret_key r, a; const char* r_hex = "4eccc86c26ac250132d141d1b447e1fe25d0d1e4f3f2d7f3aca10a2633b52808"; ASSERT_TRUE(epee::string_tools::hex_to_pod(r_hex, r)); @@ -359,6 +359,7 @@ TEST(carrot_tx_proofs, known_values_mutation_rejection_subaddress) ASSERT_TRUE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::TESTNET, recipent_address_str)); A = info.address.m_view_public_key; B = info.address.m_spend_public_key; + a = crypto::null_skey; // Compute R = rG (subaddress case) mx25519_pubkey enote_ephemeral_pubkey_out; @@ -379,7 +380,7 @@ TEST(carrot_tx_proofs, known_values_mutation_rejection_subaddress) // Generate proof with known values crypto::signature sig; - crypto::generate_carrot_tx_proof(prefix_hash, R_G, A, B, D, r, sig); + crypto::generate_carrot_tx_proof(prefix_hash, R_G, A, B, D, r, a, sig); // Verify original proof works ASSERT_TRUE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, B, D, sig));