aboutsummaryrefslogtreecommitdiff
path: root/tests/unit_tests
diff options
context:
space:
mode:
authorcenobite pinhead <pinhead@pinhead>2026-08-15 23:39:44 +0100
committercenobite pinhead <pinhead@pinhead>2026-08-15 23:39:44 +0100
commite962463c011188acdb4373f38aa892e995f27c9f (patch)
tree05f9de23f75c23d6070255891585a707f4213279 /tests/unit_tests
parent01cd9b0c3b7719ecd6ce45a32192dd1152ebd9ad (diff)
downloadmonzero-core-e962463c011188acdb4373f38aa892e995f27c9f.tar.gz
monzero-core-e962463c011188acdb4373f38aa892e995f27c9f.tar.xz
monzero-core-e962463c011188acdb4373f38aa892e995f27c9f.zip
define canonical confidential asset payloadHEADmain
Diffstat (limited to 'tests/unit_tests')
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/asset_wire.cpp169
-rw-r--r--tests/unit_tests/blockchain_db.cpp77
3 files changed, 247 insertions, 0 deletions
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index b3328fe74..52df31b4a 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -31,6 +31,7 @@ set(unit_tests_sources
apply_permutation.cpp
address_from_url.cpp
asset_confidential.cpp
+ asset_wire.cpp
asset_types.cpp
base58.cpp
blockchain_db.cpp
diff --git a/tests/unit_tests/asset_wire.cpp b/tests/unit_tests/asset_wire.cpp
new file mode 100644
index 000000000..d7c26101e
--- /dev/null
+++ b/tests/unit_tests/asset_wire.cpp
@@ -0,0 +1,169 @@
+#include "gtest/gtest.h"
+
+#include "cryptonote_basic/asset_wire.h"
+#include "ringct/bulletproofs_plus.h"
+#include "ringct/rctOps.h"
+#include "string_tools.h"
+
+namespace
+{
+ cryptonote::assets::asset_transaction_payload make_payload()
+ {
+ cryptonote::assets::asset_transaction_payload payload;
+ payload.network = cryptonote::TESTNET;
+ payload.carrier_prefix_hash.data[0] = 0xc1;
+
+ crypto::public_key issuer{};
+ crypto::secret_key issuer_secret{};
+ crypto::generate_keys(issuer, issuer_secret);
+ cryptonote::assets::issuance_payload issuance;
+ issuance.descriptor.network = cryptonote::TESTNET;
+ issuance.descriptor.issuer_key = issuer;
+ issuance.descriptor.issuance_nonce.data[0] = 0xc2;
+ issuance.descriptor.atomic_supply = 10;
+ issuance.descriptor.display_decimals = 0;
+ issuance.descriptor.metadata_reference = "ipfs://monzero-wire-vector";
+ crypto::hash authorization{};
+ if (!cryptonote::assets::derive_issuance_authorization_hash(
+ issuance.descriptor, authorization))
+ throw std::runtime_error("failed to derive issuance authorization");
+ crypto::generate_signature(authorization, issuer, issuer_secret,
+ issuance.issuer_signature);
+ payload.issuance = issuance;
+
+ crypto::hash id{};
+ if (!cryptonote::assets::derive_asset_id(issuance.descriptor, id))
+ throw std::runtime_error("failed to derive asset id");
+ cryptonote::assets::confidential_asset_balance balance;
+ balance.asset_id = id;
+ balance.pseudo_inputs.push_back({id, rct::commit(10, rct::zero())});
+ balance.outputs.push_back(rct::commit(10, rct::zero()));
+ balance.range_proofs.push_back(
+ rct::bulletproof_plus_PROVE(10, rct::zero()));
+ payload.balances.push_back(balance);
+ rct::key secret{}, destination{};
+ rct::skpkGen(secret, destination);
+ payload.output_destinations.push_back({destination});
+ return payload;
+ }
+}
+
+TEST(asset_wire, canonical_round_trip_preserves_verified_issuance)
+{
+ const auto original = make_payload();
+ std::vector<uint8_t> encoded, reencoded;
+ std::string error;
+ ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload(
+ original, encoded, &error)) << error;
+ cryptonote::assets::asset_transaction_payload decoded;
+ ASSERT_TRUE(cryptonote::assets::decode_asset_transaction_payload(
+ encoded, decoded, &error)) << error;
+ ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload(
+ decoded, reencoded, &error)) << error;
+ ASSERT_EQ(encoded, reencoded);
+ ASSERT_TRUE(decoded.issuance);
+ ASSERT_TRUE(cryptonote::assets::verify_confidential_asset_transaction(
+ decoded.balances, {}, decoded.issuance->descriptor, &error)) << error;
+ ASSERT_TRUE(cryptonote::assets::verify_asset_transaction_payload(
+ decoded, {}, cryptonote::TESTNET, decoded.carrier_prefix_hash, &error)) << error;
+ EXPECT_FALSE(cryptonote::assets::verify_asset_transaction_payload(
+ decoded, {}, cryptonote::MAINNET, decoded.carrier_prefix_hash, &error));
+ crypto::hash other_carrier = decoded.carrier_prefix_hash;
+ other_carrier.data[1] = 1;
+ EXPECT_FALSE(cryptonote::assets::verify_asset_transaction_payload(
+ decoded, {}, cryptonote::TESTNET, other_carrier, &error));
+}
+
+TEST(asset_wire, rejects_every_truncation_trailing_bytes_and_noncanonical_counts)
+{
+ const auto payload = make_payload();
+ std::vector<uint8_t> encoded;
+ std::string error;
+ ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload(
+ payload, encoded, &error)) << error;
+ for (size_t size = 0; size < encoded.size(); ++size)
+ {
+ cryptonote::assets::asset_transaction_payload decoded;
+ const std::vector<uint8_t> truncated(encoded.begin(), encoded.begin() + size);
+ EXPECT_FALSE(cryptonote::assets::decode_asset_transaction_payload(
+ truncated, decoded, &error)) << "accepted truncation at " << size;
+ }
+ auto trailing = encoded;
+ trailing.push_back(0);
+ cryptonote::assets::asset_transaction_payload decoded;
+ EXPECT_FALSE(cryptonote::assets::decode_asset_transaction_payload(
+ trailing, decoded, &error));
+ auto unsupported = encoded;
+ unsupported[0] = 2;
+ EXPECT_FALSE(cryptonote::assets::decode_asset_transaction_payload(
+ unsupported, decoded, &error));
+ auto excessive = payload;
+ excessive.balances.resize(cryptonote::assets::MAX_ASSET_BALANCE_GROUPS + 1,
+ payload.balances.front());
+ excessive.output_destinations.resize(excessive.balances.size(),
+ payload.output_destinations.front());
+ EXPECT_FALSE(cryptonote::assets::encode_asset_transaction_payload(
+ excessive, trailing, &error));
+}
+
+TEST(asset_wire, deterministic_output_identity_binds_every_field)
+{
+ const auto payload = make_payload();
+ cryptonote::assets::confidential_asset_output output{
+ payload.output_destinations.front().front(),
+ payload.balances.front().outputs.front()};
+ crypto::hash first{}, repeated{}, changed{};
+ std::string error;
+ ASSERT_TRUE(cryptonote::assets::derive_asset_output_id(
+ cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0,
+ output, first, &error)) << error;
+ ASSERT_TRUE(cryptonote::assets::derive_asset_output_id(
+ cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0,
+ output, repeated, &error));
+ EXPECT_EQ(first, repeated);
+ ASSERT_TRUE(cryptonote::assets::derive_asset_output_id(
+ cryptonote::MAINNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0,
+ output, changed, &error));
+ EXPECT_NE(first, changed);
+ ASSERT_TRUE(cryptonote::assets::derive_asset_output_id(
+ cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 1,
+ output, changed, &error));
+ EXPECT_NE(first, changed);
+ output.commitment = rct::commit(9, rct::zero());
+ ASSERT_TRUE(cryptonote::assets::derive_asset_output_id(
+ cryptonote::TESTNET, payload.carrier_prefix_hash, payload.balances.front().asset_id, 0,
+ output, changed, &error));
+ EXPECT_NE(first, changed);
+}
+
+TEST(asset_wire, fixed_wire_vector_has_stable_size_and_digest)
+{
+ cryptonote::assets::asset_transaction_payload payload;
+ payload.network = cryptonote::STAGENET;
+ payload.carrier_prefix_hash.data[0] = 0xe1;
+ cryptonote::assets::confidential_asset_balance balance;
+ balance.asset_id.data[0] = 0xe2;
+ balance.pseudo_inputs.push_back({balance.asset_id, rct::key{}});
+ balance.outputs.push_back(rct::key{});
+ rct::BulletproofPlus proof;
+ proof.A = rct::key{};
+ proof.A1 = rct::key{};
+ proof.B = rct::key{};
+ proof.r1 = rct::key{};
+ proof.s1 = rct::key{};
+ proof.d1 = rct::key{};
+ proof.V.resize(1);
+ proof.L.resize(1);
+ proof.R.resize(1);
+ balance.range_proofs.push_back(proof);
+ payload.balances.push_back(balance);
+ payload.output_destinations.push_back({rct::key{}});
+ std::vector<uint8_t> encoded;
+ std::string error;
+ ASSERT_TRUE(cryptonote::assets::encode_asset_transaction_payload(
+ payload, encoded, &error)) << error;
+ ASSERT_EQ(492u, encoded.size());
+ const crypto::hash digest = crypto::cn_fast_hash(encoded.data(), encoded.size());
+ ASSERT_EQ("7ad38e0c9b90d1d458f69df1ca5c4c27689ba0c86e0fbcb08a2149166b3d999b",
+ epee::string_tools::pod_to_hex(digest));
+}
diff --git a/tests/unit_tests/blockchain_db.cpp b/tests/unit_tests/blockchain_db.cpp
index 124a88724..a2261bc60 100644
--- a/tests/unit_tests/blockchain_db.cpp
+++ b/tests/unit_tests/blockchain_db.cpp
@@ -42,6 +42,7 @@
#include "cryptonote_basic/asset_confidential.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "device/device.hpp"
+#include "ringct/bulletproofs_plus.h"
#include "ringct/rctSigs.h"
using namespace cryptonote;
@@ -111,6 +112,30 @@ assets::asset_ownership_proof make_db_ownership_proof(
return proof;
}
+assets::asset_transaction_payload make_db_asset_issuance(
+ const crypto::hash& carrier)
+{
+ const assets::transaction_extension extension = make_asset_extension(carrier);
+ assets::asset_transaction_payload payload;
+ payload.network = TESTNET;
+ payload.carrier_prefix_hash = carrier;
+ payload.issuance = extension.issuance;
+ crypto::hash id{};
+ if (!assets::derive_asset_id(extension.issuance.descriptor, id))
+ throw std::runtime_error("failed to derive test asset id");
+ const uint64_t supply = extension.issuance.descriptor.atomic_supply;
+ assets::confidential_asset_balance balance;
+ balance.asset_id = id;
+ balance.pseudo_inputs.push_back({id, rct::commit(supply, rct::zero())});
+ balance.outputs.push_back(rct::commit(supply, rct::zero()));
+ balance.range_proofs.push_back(rct::bulletproof_plus_PROVE(supply, rct::zero()));
+ payload.balances.push_back(balance);
+ rct::key secret{}, destination{};
+ rct::skpkGen(secret, destination);
+ payload.output_destinations.push_back({destination});
+ return payload;
+}
+
const std::vector<std::string> t_blocks =
{
"0100d5adc49a053b8818b2b6023cd2d532c6774e164a8fcacd603651cb3ea0cb7f9340b28ec016b4bc4ca301aa0101ff6e08acbb2702eab03067870349139bee7eab2ca2e030a6bb73d4f68ab6a3b6ca937214054cdac0843d028bbe23b57ea9bae53f12da93bb57bf8a2e40598d9fccd10c2921576e987d93cd80b4891302468738e391f07c4f2b356f7957160968e0bfef6e907c3cee2d8c23cbf04b089680c6868f01025a0f41f063e195a966051e3a29e17130a9ce97d48f55285b9bb04bdd55a09ae78088aca3cf0202d0f26169290450fe17e08974789c3458910b4db18361cdc564f8f2d0bdd2cf568090cad2c60e02d6f3483ec45505cc3be841046c7a12bf953ac973939bc7b727e54258e1881d4d80e08d84ddcb0102dae6dfb16d3e28aaaf43e00170b90606b36f35f38f8a3dceb5ee18199dd8f17c80c0caf384a30202385d7e57a4daba4cdd9e550a92dcc188838386e7581f13f09de796cbed4716a42101c052492a077abf41996b50c1b2e67fd7288bcd8c55cdc657b4e22d0804371f6901beb76a82ea17400cd6d7f595f70e1667d2018ed8f5a78d1ce07484222618c3cd"
@@ -452,6 +477,58 @@ TYPED_TEST(BlockchainDBTest, AssetOwnershipResolvesAuthoritativeRingAndSpentStat
*this->m_db, proof, TESTNET, carrier, &error));
}
+TYPED_TEST(BlockchainDBTest, AssetTransactionStateAppliesAtomicallyAndRejectsReplay)
+{
+ const boost::filesystem::path temp_path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+ const std::string dir_path = temp_path.string();
+ this->set_prefix(dir_path);
+ ASSERT_NO_THROW(this->m_db->open(dir_path));
+ this->get_filenames();
+
+ crypto::hash carrier{};
+ carrier.data[0] = 0xd1;
+ const auto payload = make_db_asset_issuance(carrier);
+ std::vector<crypto::hash> output_ids;
+ std::string error;
+ this->m_db->block_wtxn_start();
+ ASSERT_TRUE(assets::apply_asset_transaction_to_db(*this->m_db, payload,
+ TESTNET, carrier, 50, output_ids, &error)) << error;
+ this->m_db->block_wtxn_stop();
+ ASSERT_EQ(1u, output_ids.size());
+
+ crypto::hash asset_id{};
+ ASSERT_TRUE(assets::derive_asset_id(payload.issuance->descriptor, asset_id));
+ uint64_t issuance_height = 0;
+ blobdata issuance_bytes;
+ ASSERT_TRUE(this->m_db->get_asset_record(asset_id, issuance_height, issuance_bytes));
+ ASSERT_EQ(50u, issuance_height);
+ asset_output_data_t stored{};
+ ASSERT_TRUE(this->m_db->get_asset_output(output_ids.front(), stored));
+ ASSERT_EQ(asset_id, stored.asset_id);
+ ASSERT_EQ(50u, stored.height);
+
+ std::vector<crypto::hash> replay_outputs;
+ this->m_db->block_wtxn_start();
+ EXPECT_FALSE(assets::apply_asset_transaction_to_db(*this->m_db, payload,
+ TESTNET, carrier, 51, replay_outputs, &error));
+ this->m_db->block_wtxn_abort();
+ ASSERT_TRUE(replay_outputs.empty());
+
+ auto invalid = make_db_asset_issuance(crypto::hash{});
+ crypto::hash invalid_carrier{};
+ invalid_carrier.data[0] = 0xd2;
+ invalid.carrier_prefix_hash = invalid_carrier;
+ invalid.balances.front().outputs.front() = rct::commit(
+ invalid.issuance->descriptor.atomic_supply + 1, rct::zero());
+ this->m_db->block_wtxn_start();
+ EXPECT_FALSE(assets::apply_asset_transaction_to_db(*this->m_db, invalid,
+ TESTNET, invalid_carrier, 52, replay_outputs, &error));
+ this->m_db->block_wtxn_abort();
+ crypto::hash invalid_id{};
+ ASSERT_TRUE(assets::derive_asset_id(invalid.issuance->descriptor, invalid_id));
+ ASSERT_FALSE(this->m_db->get_asset_record(invalid_id, issuance_height, issuance_bytes));
+}
+
TYPED_TEST(BlockchainDBTest, AssetBlockExtensionsRebuildFromPersistentState)
{
const boost::filesystem::path temp_path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();