aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMonzero Build System <builds@monzero.org>2026-08-15 21:57:55 +0100
committerMonzero Build System <builds@monzero.org>2026-08-15 21:57:55 +0100
commit310832fe087427fc01af504821190c2482a438d7 (patch)
tree35aea99aed4d209e7b972106da94dde6fead4bc4 /src
parente933bd36cee44b42d631c1c64e9f3df027be7ab0 (diff)
downloadmonzero-core-310832fe087427fc01af504821190c2482a438d7.tar.gz
monzero-core-310832fe087427fc01af504821190c2482a438d7.tar.xz
monzero-core-310832fe087427fc01af504821190c2482a438d7.zip
Add authenticated asset issuance payload
Diffstat (limited to 'src')
-rw-r--r--src/cryptonote_basic/asset_types.cpp101
-rw-r--r--src/cryptonote_basic/asset_types.h16
2 files changed, 117 insertions, 0 deletions
diff --git a/src/cryptonote_basic/asset_types.cpp b/src/cryptonote_basic/asset_types.cpp
index 927650b5a..05a8614b4 100644
--- a/src/cryptonote_basic/asset_types.cpp
+++ b/src/cryptonote_basic/asset_types.cpp
@@ -18,6 +18,7 @@ namespace assets
constexpr char DOMAIN[] = "MonzeroAssetIssuanceV2";
constexpr char ISSUANCE_AUTHORIZATION_DOMAIN[] = "MonzeroAssetIssuanceAuthorizationV1";
constexpr char COLLECTION_MEMBERSHIP_DOMAIN[] = "MonzeroCollectionMembershipV1";
+ constexpr char ISSUANCE_PAYLOAD_DOMAIN[] = "MonzeroAssetIssuancePayloadV1";
bool fail(std::string* error, const char* message)
{
@@ -255,6 +256,91 @@ namespace assets
return true;
}
+ bool encode_issuance_payload(const issuance_payload& payload, std::vector<uint8_t>& encoded, std::string* error)
+ {
+ if (payload.version != ISSUANCE_PAYLOAD_VERSION)
+ return fail(error, "unsupported issuance payload version");
+
+ std::vector<uint8_t> descriptor;
+ if (!encode_issuance_descriptor(payload.descriptor, descriptor, error))
+ return false;
+ if (descriptor.size() > std::numeric_limits<uint16_t>::max())
+ return fail(error, "issuance descriptor does not fit its bounded payload");
+ if (!verify_issuance_authorization(payload.descriptor, payload.issuer_signature, error))
+ return false;
+
+ const bool claims_collection = payload.descriptor.collection_id != crypto::null_hash;
+ if (claims_collection != static_cast<bool>(payload.collection_signature))
+ return fail(error, claims_collection
+ ? "collection membership signature is missing"
+ : "unexpected collection signature for an uncollected asset");
+
+ encoded.clear();
+ encoded.reserve(sizeof(ISSUANCE_PAYLOAD_DOMAIN) - 1 + 1 + 2 + descriptor.size()
+ + sizeof(payload.issuer_signature) + 1
+ + (payload.collection_signature ? sizeof(*payload.collection_signature) : 0));
+ encoded.insert(encoded.end(), ISSUANCE_PAYLOAD_DOMAIN,
+ ISSUANCE_PAYLOAD_DOMAIN + sizeof(ISSUANCE_PAYLOAD_DOMAIN) - 1);
+ encoded.push_back(payload.version);
+ append_u16_le(encoded, static_cast<uint16_t>(descriptor.size()));
+ encoded.insert(encoded.end(), descriptor.begin(), descriptor.end());
+ append_pod(encoded, payload.issuer_signature);
+ encoded.push_back(payload.collection_signature ? 1 : 0);
+ if (payload.collection_signature)
+ append_pod(encoded, *payload.collection_signature);
+ return true;
+ }
+
+ bool decode_issuance_payload(const std::vector<uint8_t>& encoded, issuance_payload& payload, std::string* error)
+ {
+ constexpr size_t domain_size = sizeof(ISSUANCE_PAYLOAD_DOMAIN) - 1;
+ constexpr size_t minimum_size = domain_size + 1 + 2 + sizeof(crypto::signature) + 1;
+ if (encoded.size() < minimum_size)
+ return fail(error, "truncated issuance payload");
+ if (!std::equal(ISSUANCE_PAYLOAD_DOMAIN,
+ ISSUANCE_PAYLOAD_DOMAIN + domain_size, encoded.begin()))
+ return fail(error, "invalid issuance payload domain");
+
+ issuance_payload parsed;
+ size_t offset = domain_size;
+ parsed.version = encoded[offset++];
+ if (parsed.version != ISSUANCE_PAYLOAD_VERSION)
+ return fail(error, "unsupported issuance payload version");
+
+ uint16_t descriptor_size = 0;
+ if (!read_u16_le(encoded, offset, descriptor_size)
+ || offset > encoded.size() || descriptor_size > encoded.size() - offset)
+ return fail(error, "invalid issuance payload descriptor length");
+ const std::vector<uint8_t> descriptor_bytes(
+ encoded.begin() + offset, encoded.begin() + offset + descriptor_size);
+ offset += descriptor_size;
+ if (!decode_issuance_descriptor(descriptor_bytes, parsed.descriptor, error))
+ return false;
+ if (!read_pod(encoded, offset, parsed.issuer_signature))
+ return fail(error, "truncated issuer authorization signature");
+ if (offset >= encoded.size())
+ return fail(error, "truncated collection signature flag");
+
+ const uint8_t has_collection_signature = encoded[offset++];
+ if (has_collection_signature > 1)
+ return fail(error, "invalid collection signature flag");
+ if (has_collection_signature)
+ {
+ crypto::signature signature{};
+ if (!read_pod(encoded, offset, signature))
+ return fail(error, "truncated collection authorization signature");
+ parsed.collection_signature = signature;
+ }
+ if (offset != encoded.size())
+ return fail(error, "issuance payload has trailing bytes");
+
+ std::vector<uint8_t> canonical;
+ if (!encode_issuance_payload(parsed, canonical, error) || canonical != encoded)
+ return fail(error, "issuance payload encoding is not canonical");
+ payload = std::move(parsed);
+ return true;
+ }
+
bool asset_registry::apply_issuance(
const issuance_descriptor& descriptor,
const crypto::signature& issuer_signature,
@@ -297,6 +383,21 @@ namespace assets
return true;
}
+ bool asset_registry::apply_issuance(
+ const issuance_payload& payload,
+ uint64_t height,
+ crypto::hash& asset_id,
+ std::string* error)
+ {
+ return apply_issuance(
+ payload.descriptor,
+ payload.issuer_signature,
+ payload.collection_signature,
+ height,
+ asset_id,
+ 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 7f2fe0c99..7265ffdf2 100644
--- a/src/cryptonote_basic/asset_types.h
+++ b/src/cryptonote_basic/asset_types.h
@@ -60,6 +60,21 @@ namespace assets
const crypto::signature& signature,
std::string* error = nullptr);
+ constexpr uint8_t ISSUANCE_PAYLOAD_VERSION = 1;
+
+ // Inactive, bounded wire prototype for an authenticated fixed-supply
+ // issuance. It is not yet a transaction field or consensus type.
+ struct issuance_payload
+ {
+ uint8_t version = ISSUANCE_PAYLOAD_VERSION;
+ issuance_descriptor descriptor;
+ crypto::signature issuer_signature{};
+ boost::optional<crypto::signature> collection_signature;
+ };
+
+ bool encode_issuance_payload(const issuance_payload& payload, std::vector<uint8_t>& encoded, std::string* error = nullptr);
+ bool decode_issuance_payload(const std::vector<uint8_t>& encoded, issuance_payload& payload, std::string* error = nullptr);
+
struct asset_record
{
issuance_descriptor descriptor;
@@ -79,6 +94,7 @@ namespace assets
uint64_t height,
crypto::hash& asset_id,
std::string* error = nullptr);
+ bool apply_issuance(const issuance_payload& payload, uint64_t height, crypto::hash& asset_id, 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;