diff options
| author | Monzero Build System <builds@monzero.org> | 2026-08-15 22:11:59 +0100 |
|---|---|---|
| committer | Monzero Build System <builds@monzero.org> | 2026-08-15 22:11:59 +0100 |
| commit | b47eefee8a8b75b7840e29e8758eb361e48b2a04 (patch) | |
| tree | 181a63e3f60127532bf9f3fbd65eff313184b5fc | |
| parent | 98b57835312d2e729eb3527be24e86002a41e202 (diff) | |
| download | monzero-core-b47eefee8a8b75b7840e29e8758eb361e48b2a04.tar.gz monzero-core-b47eefee8a8b75b7840e29e8758eb361e48b2a04.tar.xz monzero-core-b47eefee8a8b75b7840e29e8758eb361e48b2a04.zip | |
Validate asset extensions against native carriers
| -rw-r--r-- | docs/MONZERO_ASSETS_V1_SPEC.md | 7 | ||||
| -rw-r--r-- | docs/MONZERO_PHASE0_STABILIZATION.md | 1 | ||||
| -rw-r--r-- | src/cryptonote_basic/asset_types.cpp | 43 | ||||
| -rw-r--r-- | src/cryptonote_basic/asset_types.h | 12 | ||||
| -rw-r--r-- | tests/unit_tests/asset_types.cpp | 35 |
5 files changed, 98 insertions, 0 deletions
diff --git a/docs/MONZERO_ASSETS_V1_SPEC.md b/docs/MONZERO_ASSETS_V1_SPEC.md index 66bb37606..73bd27790 100644 --- a/docs/MONZERO_ASSETS_V1_SPEC.md +++ b/docs/MONZERO_ASSETS_V1_SPEC.md @@ -155,6 +155,13 @@ registered as a `tx_extra` variant and therefore does not change what active mainnet nodes accept; a reviewed hard-fork integration must define the exact non-circular carrier-prefix hashing procedure first. +The inactive block adapter additionally receives independently computed native +prefix commitments and validates each extension against its corresponding +carrier before applying any issuance. Count, network, or commitment mismatch +rejects the complete batch without changing registry state. This separates the +future native transaction parser from asset operation validation and gives +tests an explicit boundary for carrier-binding failures. + ### 4.2 Inactive registry and reorganisation model The prototype includes an in-memory reference registry, disconnected from the diff --git a/docs/MONZERO_PHASE0_STABILIZATION.md b/docs/MONZERO_PHASE0_STABILIZATION.md index aa2d73c39..13ce9ef20 100644 --- a/docs/MONZERO_PHASE0_STABILIZATION.md +++ b/docs/MONZERO_PHASE0_STABILIZATION.md @@ -132,6 +132,7 @@ isolated disposable development network may be used after cryptographic review. - [x] Deterministic authenticated registry snapshots with atomic restore failure behavior. - [x] Atomic ordered block-issuance adapter, detach behavior, and deterministic state commitments. - [x] Detached versioned transaction extension bound to a native prefix commitment. +- [x] Atomic block-extension validation against independently supplied native carrier commitments. - [x] External cryptographic-review brief and mandatory threat cases. - [ ] Reviewed confidential per-asset commitment and range-proof construction. - [ ] Versioned asset transaction serialization with strict parser limits. diff --git a/src/cryptonote_basic/asset_types.cpp b/src/cryptonote_basic/asset_types.cpp index aa17b359f..b193ec141 100644 --- a/src/cryptonote_basic/asset_types.cpp +++ b/src/cryptonote_basic/asset_types.cpp @@ -456,6 +456,24 @@ namespace assets return true; } + bool validate_transaction_extension_carrier( + const transaction_extension& extension, + network_type expected_network, + const crypto::hash& expected_prefix_hash, + std::string* error) + { + if (expected_network != MAINNET && expected_network != TESTNET && expected_network != STAGENET) + return fail(error, "asset transaction carrier validation requires an explicit network"); + if (expected_prefix_hash == crypto::null_hash) + return fail(error, "expected native transaction prefix commitment is zero"); + if (extension.network != expected_network) + return fail(error, "asset transaction extension is for another network"); + if (extension.carrier_prefix_hash != expected_prefix_hash) + return fail(error, "asset transaction extension carrier commitment mismatch"); + std::vector<uint8_t> canonical; + return encode_transaction_extension(extension, canonical, error); + } + bool asset_registry::apply_issuance( const issuance_descriptor& descriptor, const crypto::signature& issuer_signature, @@ -534,6 +552,31 @@ namespace assets return true; } + bool asset_registry::apply_block_extensions( + const std::vector<transaction_extension>& extensions, + const std::vector<crypto::hash>& carrier_prefix_hashes, + network_type expected_network, + uint64_t height, + std::vector<crypto::hash>& asset_ids, + std::string* error) + { + if (extensions.size() != carrier_prefix_hashes.size()) + return fail(error, "asset extension and carrier commitment counts differ"); + std::vector<issuance_payload> issuances; + issuances.reserve(extensions.size()); + for (size_t index = 0; index < extensions.size(); ++index) + { + const transaction_extension& extension = extensions[index]; + if (!validate_transaction_extension_carrier( + extension, expected_network, carrier_prefix_hashes[index], error)) + return false; + if (extension.operation != transaction_operation::issuance) + return fail(error, "unsupported asset block operation"); + issuances.push_back(extension.issuance); + } + return apply_block_issuances(issuances, height, asset_ids, error); + } + void asset_registry::detach(uint64_t height) { for (auto it = records_.begin(); it != records_.end();) diff --git a/src/cryptonote_basic/asset_types.h b/src/cryptonote_basic/asset_types.h index 5b2bf6006..81cbcd65d 100644 --- a/src/cryptonote_basic/asset_types.h +++ b/src/cryptonote_basic/asset_types.h @@ -96,6 +96,11 @@ namespace assets bool encode_transaction_extension(const transaction_extension& extension, std::vector<uint8_t>& encoded, std::string* error = nullptr); bool decode_transaction_extension(const std::vector<uint8_t>& encoded, transaction_extension& extension, std::string* error = nullptr); bool derive_transaction_extension_id(const transaction_extension& extension, crypto::hash& extension_id, std::string* error = nullptr); + bool validate_transaction_extension_carrier( + const transaction_extension& extension, + network_type expected_network, + const crypto::hash& expected_prefix_hash, + std::string* error = nullptr); struct asset_record { @@ -124,6 +129,13 @@ namespace assets uint64_t height, std::vector<crypto::hash>& asset_ids, std::string* error = nullptr); + bool apply_block_extensions( + const std::vector<transaction_extension>& extensions, + const std::vector<crypto::hash>& carrier_prefix_hashes, + network_type expected_network, + uint64_t height, + std::vector<crypto::hash>& asset_ids, + std::string* error = nullptr); void detach(uint64_t height); bool contains(const crypto::hash& asset_id) const; const asset_record* find(const crypto::hash& asset_id) const; diff --git a/tests/unit_tests/asset_types.cpp b/tests/unit_tests/asset_types.cpp index 5f196301d..bd34398de 100644 --- a/tests/unit_tests/asset_types.cpp +++ b/tests/unit_tests/asset_types.cpp @@ -252,6 +252,41 @@ TEST(asset_types, detached_transaction_extension_rejects_malformed_or_unbound_da EXPECT_FALSE(cryptonote::assets::encode_transaction_extension(extension, encoded, &error)); } +TEST(asset_types, block_extension_application_verifies_native_carriers_atomically) +{ + crypto::public_key issuer_public{}; + crypto::secret_key issuer_secret{}; + crypto::generate_keys(issuer_public, issuer_secret); + + cryptonote::assets::transaction_extension extension; + extension.network = cryptonote::TESTNET; + extension.carrier_prefix_hash.data[0] = 0x31; + extension.issuance.descriptor = make_descriptor(cryptonote::TESTNET); + extension.issuance.descriptor.issuer_key = issuer_public; + extension.issuance.issuer_signature = authorize( + extension.issuance.descriptor, issuer_public, issuer_secret); + + cryptonote::assets::asset_registry registry; + std::vector<crypto::hash> ids; + EXPECT_TRUE(registry.apply_block_extensions( + {extension}, {extension.carrier_prefix_hash}, cryptonote::TESTNET, 30, ids)); + EXPECT_EQ(1u, registry.size()); + EXPECT_EQ(1u, ids.size()); + + registry.detach(30); + crypto::hash wrong_carrier = extension.carrier_prefix_hash; + wrong_carrier.data[0] ^= 1; + EXPECT_FALSE(registry.apply_block_extensions( + {extension}, {wrong_carrier}, cryptonote::TESTNET, 31, ids)); + EXPECT_EQ(0u, registry.size()); + EXPECT_FALSE(registry.apply_block_extensions( + {extension}, {}, cryptonote::TESTNET, 31, ids)); + EXPECT_EQ(0u, registry.size()); + EXPECT_FALSE(registry.apply_block_extensions( + {extension}, {extension.carrier_prefix_hash}, cryptonote::STAGENET, 31, ids)); + EXPECT_EQ(0u, registry.size()); +} + TEST(asset_types, network_domain_separation) { auto mainnet = make_descriptor(cryptonote::MAINNET); |
