aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMonzero Build System <builds@monzero.org>2026-08-15 22:11:59 +0100
committerMonzero Build System <builds@monzero.org>2026-08-15 22:11:59 +0100
commitb47eefee8a8b75b7840e29e8758eb361e48b2a04 (patch)
tree181a63e3f60127532bf9f3fbd65eff313184b5fc
parent98b57835312d2e729eb3527be24e86002a41e202 (diff)
downloadmonzero-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.md7
-rw-r--r--docs/MONZERO_PHASE0_STABILIZATION.md1
-rw-r--r--src/cryptonote_basic/asset_types.cpp43
-rw-r--r--src/cryptonote_basic/asset_types.h12
-rw-r--r--tests/unit_tests/asset_types.cpp35
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);