From 5514712fdb17c1bd37fc264d66bae7e091d004cc Mon Sep 17 00:00:00 2001 From: Rhett Creighton Date: Sat, 6 Jun 2026 01:37:04 +0000 Subject: [PATCH] zslp: port ZSLP SLP Token Type 1 protocol library + gtest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure code-add, NON-consensus. Ports the Apache-2.0 ZSLP protocol library from github.com/RhettCreighton/zclassic-c into src/zslp/: - op_return_push.h — raw-buffer Bitcoin script PUSH encode/decode - slp.h / slp.c — SLP Token Type 1 parser + GENESIS/MINT/SEND builders - uint256_c.h — the plain-C `struct uint256` the C library needs, ported verbatim from the reference's core/uint256.h with a renamed guard (ZSLP_UINT256_C_H) so it never collides with the daemon's C++ class uint256 Adjusted ONLY include paths to the flat src/zslp/ layout; no logic changes. Added C-linkage (extern "C") guards so the .c compiles as C while its headers are consumed from C++. Build wiring: - src/Makefile.am: zslp/slp.c -> libbitcoin_common_a_SOURCES; zslp/{slp,op_return_push,uint256_c}.h -> BITCOIN_CORE_H - src/Makefile.gtest.include: gtest/test_zslp.cpp -> zcash_gtest_SOURCES; -I$(srcdir)/zslp on zcash_gtest_CPPFLAGS Tests: src/gtest/test_zslp.cpp ports lib/test/src/test_slp.c to gtest, same GENESIS/MINT/SEND build+parse round-trips and edge cases. Decimals note: the slp.c parser accepts decimals 0-9 per the SLP spec (unchanged from the reference); the not-yet-ported model used 0-8. Attribution: src/zslp/LICENSE (Apache 2.0) + src/zslp/NOTICE. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Makefile.am | 6 +- src/Makefile.gtest.include | 5 +- src/gtest/test_zslp.cpp | 433 +++++++++++++++++++++++++++++++++++++ src/zslp/LICENSE | 202 +++++++++++++++++ src/zslp/NOTICE | 1 + src/zslp/op_return_push.h | 106 +++++++++ src/zslp/slp.c | 292 +++++++++++++++++++++++++ src/zslp/slp.h | 101 +++++++++ src/zslp/uint256_c.h | 88 ++++++++ 9 files changed, 1231 insertions(+), 3 deletions(-) create mode 100644 src/gtest/test_zslp.cpp create mode 100644 src/zslp/LICENSE create mode 100644 src/zslp/NOTICE create mode 100644 src/zslp/op_return_push.h create mode 100644 src/zslp/slp.c create mode 100644 src/zslp/slp.h create mode 100644 src/zslp/uint256_c.h diff --git a/src/Makefile.am b/src/Makefile.am index da653f2be98..fca9356e7e7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -235,7 +235,10 @@ BITCOIN_CORE_H = \ zmq/zmqabstractnotifier.h \ zmq/zmqconfig.h\ zmq/zmqnotificationinterface.h \ - zmq/zmqpublishnotifier.h + zmq/zmqpublishnotifier.h \ + zslp/slp.h \ + zslp/op_return_push.h \ + zslp/uint256_c.h obj/build.h: FORCE @@ -395,6 +398,7 @@ libbitcoin_common_a_SOURCES = \ script/standard.cpp \ transaction_builder.cpp \ utiltest.cpp \ + zslp/slp.c \ $(BITCOIN_CORE_H) \ $(LIBZCASH_H) diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index 07d4090b802..bd26107393c 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -44,14 +44,15 @@ zcash_gtest_SOURCES += \ gtest/test_proofs.cpp \ gtest/test_pedersen_hash.cpp \ gtest/test_checkblock.cpp \ - gtest/test_zip32.cpp + gtest/test_zip32.cpp \ + gtest/test_zslp.cpp if ENABLE_WALLET zcash_gtest_SOURCES += \ wallet/gtest/test_paymentdisclosure.cpp \ wallet/gtest/test_wallet.cpp endif -zcash_gtest_CPPFLAGS = $(AM_CPPFLAGS) -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES) +zcash_gtest_CPPFLAGS = $(AM_CPPFLAGS) -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES) -I$(srcdir)/zslp zcash_gtest_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) zcash_gtest_LDADD = -lgtest -lgmock $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ diff --git a/src/gtest/test_zslp.cpp b/src/gtest/test_zslp.cpp new file mode 100644 index 00000000000..164271ba0ed --- /dev/null +++ b/src/gtest/test_zslp.cpp @@ -0,0 +1,433 @@ +// Copyright 2026 Rhett Creighton - Apache License 2.0 +// +// Tests for the ported ZSLP Simple Ledger Protocol (SLP) parser and builders. +// Ported from github.com/RhettCreighton/zclassic-c (lib/test/src/test_slp.c), +// translated from the reference's hand-rolled assertions to GoogleTest. +// Covers the same GENESIS / MINT / SEND build + parse round-trips and edge +// cases as the reference. + +#include + +#include +#include + +#include "zslp/slp.h" + +// ── Parse valid GENESIS ─────────────────────────────────────────── + +TEST(ZSLP, ParseValidGenesis) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "ZCL", "ZClassic Token", "https://zclassic.org", nullptr, + 8, 0, 1000000); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_GENESIS); + EXPECT_EQ(msg.token_type, SLP_TOKEN_TYPE_1); + EXPECT_STREQ(msg.ticker, "ZCL"); + EXPECT_STREQ(msg.name, "ZClassic Token"); + EXPECT_STREQ(msg.document_url, "https://zclassic.org"); + EXPECT_FALSE(msg.has_document_hash); + EXPECT_EQ(msg.decimals, 8); + EXPECT_EQ(msg.mint_baton_vout, 0); + EXPECT_EQ(msg.initial_quantity, 1000000u); +} + +TEST(ZSLP, ParseGenesisWithDocumentHash) +{ + uint8_t hash[32]; + memset(hash, 0xAB, 32); + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "TEST", "Test Token", "https://example.com", hash, + 4, 2, 5000); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_GENESIS); + EXPECT_TRUE(msg.has_document_hash); + EXPECT_EQ(memcmp(msg.document_hash, hash, 32), 0); + EXPECT_EQ(msg.decimals, 4); + EXPECT_EQ(msg.mint_baton_vout, 2); + EXPECT_EQ(msg.initial_quantity, 5000u); +} + +// ── Parse valid MINT ────────────────────────────────────────────── + +TEST(ZSLP, ParseValidMint) +{ + struct uint256 token_id; + memset(token_id.data, 0xCC, 32); + uint8_t buf[256]; + size_t len = slp_build_mint(buf, sizeof(buf), &token_id, 2, 999999); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_MINT); + EXPECT_EQ(msg.token_type, SLP_TOKEN_TYPE_1); + EXPECT_EQ(memcmp(msg.token_id.data, token_id.data, 32), 0); + EXPECT_EQ(msg.mint_baton_vout, 2); + EXPECT_EQ(msg.additional_quantity, 999999u); +} + +TEST(ZSLP, ParseMintNoBaton) +{ + struct uint256 token_id; + memset(token_id.data, 0xDD, 32); + uint8_t buf[256]; + size_t len = slp_build_mint(buf, sizeof(buf), &token_id, 0, 42); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_MINT); + EXPECT_EQ(msg.mint_baton_vout, 0); + EXPECT_EQ(msg.additional_quantity, 42u); +} + +// ── Parse valid SEND ────────────────────────────────────────────── + +TEST(ZSLP, ParseValidSendOneOutput) +{ + struct uint256 token_id; + memset(token_id.data, 0xEE, 32); + uint64_t qty[] = { 100 }; + uint8_t buf[256]; + size_t len = slp_build_send(buf, sizeof(buf), &token_id, qty, 1); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_SEND); + EXPECT_EQ(msg.num_outputs, 1); + EXPECT_EQ(msg.output_quantities[0], 100u); +} + +TEST(ZSLP, ParseValidSendMultipleOutputs) +{ + struct uint256 token_id; + memset(token_id.data, 0xFF, 32); + uint64_t qty[] = { 10, 20, 30, 40, 50 }; + uint8_t buf[512]; + size_t len = slp_build_send(buf, sizeof(buf), &token_id, qty, 5); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_SEND); + EXPECT_EQ(msg.num_outputs, 5); + for (int i = 0; i < 5; i++) + EXPECT_EQ(msg.output_quantities[i], qty[i]); +} + +// ── NULL / empty / too-short / malformed scripts ────────────────── + +TEST(ZSLP, ParseNullScript) +{ + struct slp_message msg; + EXPECT_FALSE(slp_parse(nullptr, 0, &msg)); + EXPECT_EQ(msg.type, SLP_TX_INVALID); +} + +TEST(ZSLP, ParseEmptyScript) +{ + uint8_t buf[1] = { 0 }; + struct slp_message msg; + EXPECT_FALSE(slp_parse(buf, 0, &msg)); + EXPECT_EQ(msg.type, SLP_TX_INVALID); +} + +TEST(ZSLP, ParseTooShortScriptJustOpReturn) +{ + uint8_t buf[] = { 0x6a }; + struct slp_message msg; + EXPECT_FALSE(slp_parse(buf, 1, &msg)); +} + +TEST(ZSLP, ParseScriptNotStartingWithOpReturn) +{ + uint8_t buf[] = { 0x00, 0x04, 'S', 'L', 'P', 0x00 }; + struct slp_message msg; + EXPECT_FALSE(slp_parse(buf, sizeof(buf), &msg)); +} + +// ── Invalid lokad_id ────────────────────────────────────────────── + +TEST(ZSLP, ParseInvalidLokadId) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "X", "X", "", nullptr, 0, 0, 1); + ASSERT_GT(len, 0u); + // lokad_id is at offset 2 (after OP_RETURN + push opcode) + buf[2] = 'X'; // corrupt first byte of "SLP\0" + struct slp_message msg; + EXPECT_FALSE(slp_parse(buf, len, &msg)); +} + +// ── Wrong token_type ────────────────────────────────────────────── + +TEST(ZSLP, ParseWrongTokenType) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "X", "X", "", nullptr, 0, 0, 1); + ASSERT_GT(len, 0u); + // lokad_id: OP_RETURN(1) + push(1) + 4 bytes = offset 6 + // token_type: push(1) + 1 byte at offset 7 + buf[7] = 2; // change token_type from 1 to 2 + struct slp_message msg; + EXPECT_FALSE(slp_parse(buf, len, &msg)); +} + +// ── Build + parse round-trips ───────────────────────────────────── + +TEST(ZSLP, BuildGenesisRoundTrip) +{ + uint8_t hash[32]; + for (int i = 0; i < 32; i++) hash[i] = (uint8_t)i; + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "MYTOKEN", "My Token Name", "https://mytoken.org", hash, + 6, 3, UINT64_C(1000000000000)); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_GENESIS); + EXPECT_STREQ(msg.ticker, "MYTOKEN"); + EXPECT_STREQ(msg.name, "My Token Name"); + EXPECT_STREQ(msg.document_url, "https://mytoken.org"); + EXPECT_TRUE(msg.has_document_hash); + EXPECT_EQ(memcmp(msg.document_hash, hash, 32), 0); + EXPECT_EQ(msg.decimals, 6); + EXPECT_EQ(msg.mint_baton_vout, 3); + EXPECT_EQ(msg.initial_quantity, UINT64_C(1000000000000)); +} + +TEST(ZSLP, BuildMintRoundTrip) +{ + struct uint256 token_id; + for (int i = 0; i < 32; i++) token_id.data[i] = (uint8_t)(0x10 + i); + uint8_t buf[256]; + size_t len = slp_build_mint(buf, sizeof(buf), &token_id, 4, + UINT64_C(9999999999)); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_MINT); + EXPECT_EQ(memcmp(msg.token_id.data, token_id.data, 32), 0); + EXPECT_EQ(msg.mint_baton_vout, 4); + EXPECT_EQ(msg.additional_quantity, UINT64_C(9999999999)); +} + +TEST(ZSLP, BuildSendRoundTrip) +{ + struct uint256 token_id; + memset(token_id.data, 0x55, 32); + uint64_t qty[] = { 100, 200, 300 }; + uint8_t buf[512]; + size_t len = slp_build_send(buf, sizeof(buf), &token_id, qty, 3); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_SEND); + EXPECT_EQ(msg.num_outputs, 3); + EXPECT_EQ(msg.output_quantities[0], 100u); + EXPECT_EQ(msg.output_quantities[1], 200u); + EXPECT_EQ(msg.output_quantities[2], 300u); +} + +// ── Edge cases: ticker lengths ──────────────────────────────────── + +TEST(ZSLP, BuildGenesisEmptyTicker) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "", "No Ticker", "", nullptr, 0, 0, 1); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_GENESIS); + EXPECT_EQ(msg.ticker[0], '\0'); // empty ticker + EXPECT_EQ(msg.initial_quantity, 1u); +} + +TEST(ZSLP, BuildGenesisNullTicker) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + nullptr, "Null Ticker", "", nullptr, 0, 0, 1); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_GENESIS); + EXPECT_EQ(msg.ticker[0], '\0'); +} + +TEST(ZSLP, BuildGenesisMaxLengthTicker63Chars) +{ + char long_ticker[64]; + memset(long_ticker, 'Z', 63); + long_ticker[63] = '\0'; + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + long_ticker, "Long", "", nullptr, 0, 0, 1); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_GENESIS); + EXPECT_STREQ(msg.ticker, long_ticker); +} + +// ── Edge cases: decimals ────────────────────────────────────────── + +TEST(ZSLP, BuildGenesisZeroDecimals) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "NDC", "No Decimals Coin", "", nullptr, 0, 0, 100); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.decimals, 0); +} + +TEST(ZSLP, BuildGenesisEightDecimals) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "BTC", "Bitcoin Clone", "", nullptr, 8, 0, 2100000000000000ULL); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.decimals, 8); + EXPECT_EQ(msg.initial_quantity, 2100000000000000ULL); +} + +// ── Edge cases: SEND outputs ────────────────────────────────────── + +TEST(ZSLP, BuildSendZeroOutputsShouldFail) +{ + struct uint256 token_id; + memset(token_id.data, 0x11, 32); + uint64_t qty[] = { 0 }; + uint8_t buf[256]; + size_t len = slp_build_send(buf, sizeof(buf), &token_id, qty, 0); + EXPECT_EQ(len, 0u); // builder should reject 0 outputs +} + +TEST(ZSLP, BuildSendNineteenOutputsMax) +{ + struct uint256 token_id; + memset(token_id.data, 0x22, 32); + uint64_t qty[19]; + for (int i = 0; i < 19; i++) qty[i] = (uint64_t)(i + 1) * 100; + uint8_t buf[1024]; + size_t len = slp_build_send(buf, sizeof(buf), &token_id, qty, 19); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_SEND); + EXPECT_EQ(msg.num_outputs, 19); + for (int i = 0; i < 19; i++) + EXPECT_EQ(msg.output_quantities[i], qty[i]); +} + +TEST(ZSLP, BuildSendTwentyOutputsOverMaxShouldFail) +{ + struct uint256 token_id; + memset(token_id.data, 0x33, 32); + uint64_t qty[20]; + for (int i = 0; i < 20; i++) qty[i] = 1; + uint8_t buf[1024]; + size_t len = slp_build_send(buf, sizeof(buf), &token_id, qty, 20); + EXPECT_EQ(len, 0u); // builder should reject >19 outputs +} + +// ── Large quantity (UINT64_MAX) ─────────────────────────────────── + +TEST(ZSLP, BuildGenesisMaxQuantity) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "MAX", "Max Supply", "", nullptr, 0, 0, UINT64_MAX); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.initial_quantity, UINT64_MAX); +} + +TEST(ZSLP, BuildMintMaxQuantity) +{ + struct uint256 token_id; + memset(token_id.data, 0xAA, 32); + uint8_t buf[256]; + size_t len = slp_build_mint(buf, sizeof(buf), &token_id, 0, UINT64_MAX); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.additional_quantity, UINT64_MAX); +} + +// ── Zero quantity ───────────────────────────────────────────────── + +TEST(ZSLP, BuildGenesisZeroQuantity) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "ZERO", "Zero Token", "", nullptr, 0, 0, 0); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.initial_quantity, 0u); +} + +// ── Truncated GENESIS (cut off before initial_quantity) ─────────── + +TEST(ZSLP, ParseTruncatedGenesis) +{ + uint8_t buf[512]; + size_t len = slp_build_genesis(buf, sizeof(buf), + "X", "X", "", nullptr, 0, 0, 1); + ASSERT_GT(len, 10u); + // Truncate to remove the last field (initial_quantity) + struct slp_message msg; + EXPECT_FALSE(slp_parse(buf, len - 10, &msg)); +} + +// ── SEND with zero-value quantities ─────────────────────────────── + +TEST(ZSLP, BuildSendWithZeroQuantities) +{ + struct uint256 token_id; + memset(token_id.data, 0x44, 32); + uint64_t qty[] = { 0, 0, 0 }; + uint8_t buf[512]; + size_t len = slp_build_send(buf, sizeof(buf), &token_id, qty, 3); + ASSERT_GT(len, 0u); + + struct slp_message msg; + ASSERT_TRUE(slp_parse(buf, len, &msg)); + EXPECT_EQ(msg.type, SLP_TX_SEND); + EXPECT_EQ(msg.num_outputs, 3); + EXPECT_EQ(msg.output_quantities[0], 0u); + EXPECT_EQ(msg.output_quantities[1], 0u); + EXPECT_EQ(msg.output_quantities[2], 0u); +} diff --git a/src/zslp/LICENSE b/src/zslp/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/src/zslp/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/zslp/NOTICE b/src/zslp/NOTICE new file mode 100644 index 00000000000..b5489277a06 --- /dev/null +++ b/src/zslp/NOTICE @@ -0,0 +1 @@ +ZSLP protocol library (src/zslp/) (c) 2026 Rhett Creighton, Apache License 2.0, ported from github.com/RhettCreighton/zclassic-c. diff --git a/src/zslp/op_return_push.h b/src/zslp/op_return_push.h new file mode 100644 index 00000000000..9e0884a0e9c --- /dev/null +++ b/src/zslp/op_return_push.h @@ -0,0 +1,106 @@ +/* Copyright 2026 Rhett Creighton - Apache License 2.0 + * + * Shared OP_RETURN push helpers — raw-buffer Bitcoin script PUSH + * encode/decode used by the on-chain data protocols (ZSLP, ZNAM). + * + * Shared by lib/zslp/src/slp.c and lib/znam/src/znam.c so both encode/decode + * OP_RETURN pushes through one definition. */ + +#ifndef SCRIPT_OP_RETURN_PUSH_H +#define SCRIPT_OP_RETURN_PUSH_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Read a Bitcoin script PUSH data field. + * Returns pointer past the field, or NULL on error. + * Sets *data and *len to the pushed bytes. */ +static inline const uint8_t *read_push(const uint8_t *p, const uint8_t *end, + const uint8_t **data, size_t *len) +{ + if (p >= end) return NULL; + uint8_t opcode = *p++; + + if (opcode >= 0x01 && opcode <= 0x4b) { + *len = opcode; + } else if (opcode == 0x4c) { /* OP_PUSHDATA1 */ + if (p >= end) return NULL; + *len = *p++; + } else if (opcode == 0x4d) { /* OP_PUSHDATA2 */ + if (p + 2 > end) return NULL; + *len = (size_t)p[0] | ((size_t)p[1] << 8); + p += 2; + } else { + return NULL; /* invalid push opcode */ + } + + if (p + *len > end) return NULL; + *data = p; + return p + *len; +} + +/* Encoded byte count of a PUSH field (prefix + payload), without writing. */ +static inline size_t push_data_size(size_t len) +{ + size_t prefix = (len <= 0x4b) ? 1 : (len <= 0xff) ? 2 : 3; + return prefix + len; +} + +/* Encode a PUSH data field into out, returning the bytes written. */ +static inline size_t push_data(uint8_t *out, const uint8_t *data, size_t len) +{ + size_t off = 0; + if (len <= 0x4b) { + out[off++] = (uint8_t)len; + } else if (len <= 0xff) { + out[off++] = 0x4c; + out[off++] = (uint8_t)len; + } else { + out[off++] = 0x4d; + out[off++] = (uint8_t)(len & 0xff); + out[off++] = (uint8_t)((len >> 8) & 0xff); + } + memcpy(out + off, data, len); + return off + len; +} + +/* Emit an empty push (OP_PUSHDATA1 with length 0x00). */ +static inline size_t push_empty(uint8_t *out) +{ + out[0] = 0x4c; + out[1] = 0x00; + return 2; +} + +/* Bounded PUSH encode. Writes at *off only if the field fits within cap; + * advances *off and returns true on success, returns false (leaving *off + * untouched) when the field would overflow the caller's buffer. Produces + * the same bytes as push_data for every in-bounds field. */ +static inline bool push_data_checked(uint8_t *out, size_t *off, size_t cap, + const uint8_t *data, size_t len) +{ + size_t need = push_data_size(len); + if (need > cap - *off) return false; + *off += push_data(out + *off, data, len); + return true; +} + +/* Bounded empty-PUSH encode. Same contract as push_data_checked. */ +static inline bool push_empty_checked(uint8_t *out, size_t *off, size_t cap) +{ + if (2 > cap - *off) return false; + *off += push_empty(out + *off); + return true; +} + +#ifdef __cplusplus +} +#endif + +#endif /* SCRIPT_OP_RETURN_PUSH_H */ diff --git a/src/zslp/slp.c b/src/zslp/slp.c new file mode 100644 index 00000000000..5cba92e1b29 --- /dev/null +++ b/src/zslp/slp.c @@ -0,0 +1,292 @@ +/* Copyright 2026 Rhett Creighton - Apache License 2.0 + * + * Simple Ledger Protocol (SLP) — Token Type 1 parser and builder. + * Based on the SLP specification from Bitcoin Cash, adapted for ZClassic. + * + * OP_RETURN format: + * OP_RETURN + * [...] — fields depend on tx_type + * + * All integer fields are big-endian. */ + +#include "slp.h" +#include "op_return_push.h" +#include + +/* Read a big-endian uint64 from data of given length (1-8 bytes). */ +static uint64_t be_to_u64(const uint8_t *data, size_t len) +{ + uint64_t val = 0; + for (size_t i = 0; i < len && i < 8; i++) + val = (val << 8) | data[i]; + return val; +} + +/* Write a big-endian uint64 (8 bytes). */ +static void u64_to_be(uint8_t *out, uint64_t val) +{ + for (int i = 7; i >= 0; i--) { + out[i] = (uint8_t)(val & 0xff); + val >>= 8; + } +} + +bool slp_parse(const uint8_t *script, size_t script_len, + struct slp_message *msg) +{ + memset(msg, 0, sizeof(*msg)); + msg->type = SLP_TX_INVALID; + + const uint8_t *p = script; + const uint8_t *end = script + script_len; + + /* Must start with OP_RETURN (0x6a) */ + if (p >= end || *p != 0x6a) return false; + p++; + + /* Field 0: lokad_id — must be "SLP\0" (4 bytes) */ + const uint8_t *data; size_t len; + p = read_push(p, end, &data, &len); + if (!p || len != 4 || memcmp(data, SLP_LOKAD_BYTES, 4) != 0) + return false; + + /* Field 1: token_type — must be 1 (1-2 bytes) */ + p = read_push(p, end, &data, &len); + if (!p || len < 1 || len > 2) return false; + msg->token_type = (uint8_t)be_to_u64(data, len); + if (msg->token_type != SLP_TOKEN_TYPE_1) return false; + + /* Field 2: transaction_type */ + p = read_push(p, end, &data, &len); + if (!p) return false; + + if (len == 7 && memcmp(data, "GENESIS", 7) == 0) { + msg->type = SLP_TX_GENESIS; + + /* Field 3: ticker */ + p = read_push(p, end, &data, &len); + if (!p) return false; + if (len > 0 && len < sizeof(msg->ticker)) { + memcpy(msg->ticker, data, len); + msg->ticker[len] = 0; + } + + /* Field 4: name */ + p = read_push(p, end, &data, &len); + if (!p) return false; + if (len > 0 && len < sizeof(msg->name)) { + memcpy(msg->name, data, len); + msg->name[len] = 0; + } + + /* Field 5: document_url */ + p = read_push(p, end, &data, &len); + if (!p) return false; + if (len > 0 && len < sizeof(msg->document_url)) { + memcpy(msg->document_url, data, len); + msg->document_url[len] = 0; + } + + /* Field 6: document_hash (0 or 32 bytes) */ + p = read_push(p, end, &data, &len); + if (!p) return false; + if (len == 32) { + memcpy(msg->document_hash, data, 32); + msg->has_document_hash = true; + } + + /* Field 7: decimals (1 byte, 0-9) */ + p = read_push(p, end, &data, &len); + if (!p || len != 1 || data[0] > 9) return false; + msg->decimals = data[0]; + + /* Field 8: mint_baton_vout (0 or 1 byte) */ + p = read_push(p, end, &data, &len); + if (!p) return false; + if (len == 1) { + if (data[0] < 2) return false; /* vout must be >= 2 */ + msg->mint_baton_vout = data[0]; + } + + /* Field 9: initial_token_mint_quantity (8 bytes) */ + p = read_push(p, end, &data, &len); + if (!p || len != 8) return false; + msg->initial_quantity = be_to_u64(data, 8); + + return true; + + } else if (len == 4 && memcmp(data, "MINT", 4) == 0) { + msg->type = SLP_TX_MINT; + + /* Field 3: token_id (32 bytes) */ + p = read_push(p, end, &data, &len); + if (!p || len != 32) return false; + memcpy(msg->token_id.data, data, 32); + + /* Field 4: mint_baton_vout */ + p = read_push(p, end, &data, &len); + if (!p) return false; + if (len == 1) { + if (data[0] < 2) return false; + msg->mint_baton_vout = data[0]; + } + + /* Field 5: additional_token_quantity (8 bytes) */ + p = read_push(p, end, &data, &len); + if (!p || len != 8) return false; + msg->additional_quantity = be_to_u64(data, 8); + + return true; + + } else if (len == 4 && memcmp(data, "SEND", 4) == 0) { + msg->type = SLP_TX_SEND; + + /* Field 3: token_id (32 bytes) */ + p = read_push(p, end, &data, &len); + if (!p || len != 32) return false; + memcpy(msg->token_id.data, data, 32); + + /* Fields 4+: output quantities (8 bytes each, 1-19 outputs) */ + msg->num_outputs = 0; + while (msg->num_outputs < 19) { + const uint8_t *saved = p; + p = read_push(p, end, &data, &len); + if (!p || len != 8) { + p = saved; /* restore for check below */ + break; + } + msg->output_quantities[msg->num_outputs++] = be_to_u64(data, 8); + } + if (msg->num_outputs < 1) return false; + + return true; + } + + return false; +} + +/* ── Builders ────────────────────────────────────────────────── */ + +size_t slp_build_genesis(uint8_t *out, size_t out_len, + const char *ticker, const char *name, + const char *document_url, + const uint8_t *document_hash, + uint8_t decimals, uint8_t mint_baton_vout, + uint64_t initial_quantity) +{ + if (out_len < 1) return 0; + size_t off = 0; + out[off++] = 0x6a; /* OP_RETURN */ + + bool ok = true; + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)SLP_LOKAD_BYTES, 4); + + uint8_t tt = 1; + ok = ok && push_data_checked(out, &off, out_len, &tt, 1); + + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)"GENESIS", 7); + + /* ticker */ + if (ticker && ticker[0]) + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)ticker, strlen(ticker)); + else + ok = ok && push_empty_checked(out, &off, out_len); + + /* name */ + if (name && name[0]) + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)name, strlen(name)); + else + ok = ok && push_empty_checked(out, &off, out_len); + + /* document_url */ + if (document_url && document_url[0]) + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)document_url, + strlen(document_url)); + else + ok = ok && push_empty_checked(out, &off, out_len); + + /* document_hash */ + if (document_hash) + ok = ok && push_data_checked(out, &off, out_len, document_hash, 32); + else + ok = ok && push_empty_checked(out, &off, out_len); + + /* decimals */ + ok = ok && push_data_checked(out, &off, out_len, &decimals, 1); + + /* mint_baton_vout */ + if (mint_baton_vout >= 2) + ok = ok && push_data_checked(out, &off, out_len, &mint_baton_vout, 1); + else + ok = ok && push_empty_checked(out, &off, out_len); + + /* initial_quantity */ + uint8_t qty[8]; + u64_to_be(qty, initial_quantity); + ok = ok && push_data_checked(out, &off, out_len, qty, 8); + + return ok ? off : 0; +} + +size_t slp_build_mint(uint8_t *out, size_t out_len, + const struct uint256 *token_id, + uint8_t mint_baton_vout, + uint64_t additional_quantity) +{ + if (out_len < 1) return 0; + size_t off = 0; + out[off++] = 0x6a; + + bool ok = true; + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)SLP_LOKAD_BYTES, 4); + uint8_t tt = 1; + ok = ok && push_data_checked(out, &off, out_len, &tt, 1); + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)"MINT", 4); + ok = ok && push_data_checked(out, &off, out_len, token_id->data, 32); + + if (mint_baton_vout >= 2) + ok = ok && push_data_checked(out, &off, out_len, &mint_baton_vout, 1); + else + ok = ok && push_empty_checked(out, &off, out_len); + + uint8_t qty[8]; + u64_to_be(qty, additional_quantity); + ok = ok && push_data_checked(out, &off, out_len, qty, 8); + + return ok ? off : 0; +} + +size_t slp_build_send(uint8_t *out, size_t out_len, + const struct uint256 *token_id, + const uint64_t *quantities, int num_outputs) +{ + if (num_outputs < 1 || num_outputs > 19) return 0; + if (out_len < 1) return 0; + + size_t off = 0; + out[off++] = 0x6a; + + bool ok = true; + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)SLP_LOKAD_BYTES, 4); + uint8_t tt = 1; + ok = ok && push_data_checked(out, &off, out_len, &tt, 1); + ok = ok && push_data_checked(out, &off, out_len, + (const uint8_t *)"SEND", 4); + ok = ok && push_data_checked(out, &off, out_len, token_id->data, 32); + + for (int i = 0; i < num_outputs; i++) { + uint8_t qty[8]; + u64_to_be(qty, quantities[i]); + ok = ok && push_data_checked(out, &off, out_len, qty, 8); + } + + return ok ? off : 0; +} diff --git a/src/zslp/slp.h b/src/zslp/slp.h new file mode 100644 index 00000000000..6ebf824b881 --- /dev/null +++ b/src/zslp/slp.h @@ -0,0 +1,101 @@ +/* Copyright 2026 Rhett Creighton - Apache License 2.0 + * + * Simple Ledger Protocol (SLP) — Token support for ZClassic. + * Based on SLP Token Type 1 specification from Bitcoin Cash. + * Tokens are encoded in OP_RETURN outputs (vout[0]). + * + * Operations: GENESIS (create), MINT (issue more), SEND (transfer), BURN. + * Lokad ID: "SLP\x00" (0x534c5000) */ + +#ifndef ZCL_ZSLP_SLP_H +#define ZCL_ZSLP_SLP_H + +#include +#include +#include +#include "uint256_c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* SLP Lokad ID */ +#define SLP_LOKAD_ID 0x00504c53 /* "SLP\0" little-endian */ +#define SLP_LOKAD_BYTES "\x53\x4c\x50\x00" /* "SLP\0" */ + +/* Token type */ +#define SLP_TOKEN_TYPE_1 1 + +/* Transaction types */ +enum slp_tx_type { + SLP_TX_GENESIS = 1, + SLP_TX_MINT = 2, + SLP_TX_SEND = 3, + SLP_TX_BURN = 4, /* implicit — send less than input */ + SLP_TX_INVALID = 0, +}; + +/* Parsed SLP message from OP_RETURN */ +struct slp_message { + enum slp_tx_type type; + uint8_t token_type; /* always 1 for now */ + + /* GENESIS fields */ + char ticker[64]; + char name[128]; + char document_url[256]; + uint8_t document_hash[32]; + bool has_document_hash; + uint8_t decimals; + uint8_t mint_baton_vout; /* 0 = no baton */ + uint64_t initial_quantity; + + /* MINT fields */ + struct uint256 token_id; + uint64_t additional_quantity; + /* mint_baton_vout reused */ + + /* SEND fields */ + /* token_id reused */ + uint64_t output_quantities[20]; /* vout[1]..vout[19] + 1 extra */ + int num_outputs; +}; + +/* Parse an OP_RETURN script into an SLP message. + * Returns true if the script is a valid SLP message. */ +bool slp_parse(const uint8_t *script, size_t script_len, + struct slp_message *msg); + +/* Build OP_RETURN scripts for each transaction type. + * Caller provides the output buffer; all builders return the number of + * bytes written, or 0 if the buffer is too small (or, where noted, on + * invalid input). Token type is always 1. */ + +/* GENESIS: create a token. ticker/name/document_url may be NULL/empty + * (encoded as empty pushes); document_hash, if non-NULL, is 32 bytes. + * mint_baton_vout is emitted only when >= 2 (0/1 mean no baton). */ +size_t slp_build_genesis(uint8_t *out, size_t out_len, + const char *ticker, const char *name, + const char *document_url, + const uint8_t *document_hash, + uint8_t decimals, uint8_t mint_baton_vout, + uint64_t initial_quantity); + +/* MINT: issue more of an existing token. token_id is required (32 bytes). + * mint_baton_vout is emitted only when >= 2 (0/1 mean no baton). */ +size_t slp_build_mint(uint8_t *out, size_t out_len, + const struct uint256 *token_id, + uint8_t mint_baton_vout, + uint64_t additional_quantity); + +/* SEND: transfer token amounts. token_id and the quantities array are + * required; num_outputs must be in 1..19 (returns 0 otherwise). */ +size_t slp_build_send(uint8_t *out, size_t out_len, + const struct uint256 *token_id, + const uint64_t *quantities, int num_outputs); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/zslp/uint256_c.h b/src/zslp/uint256_c.h new file mode 100644 index 00000000000..14080930a24 --- /dev/null +++ b/src/zslp/uint256_c.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2009-2010 Satoshi Nakamoto + * Copyright (c) 2009-2014 The Bitcoin Core developers + * Copyright 2026 Rhett Creighton - Apache License 2.0 + * Distributed under the MIT software license, see the accompanying + * file COPYING or http://www.opensource.org/licenses/mit-license.php. + * + * Ported verbatim from github.com/RhettCreighton/zclassic-c + * (lib/core/include/core/uint256.h). The plain-C `struct uint256` + * used by the ZSLP protocol library. Renamed include guard to + * ZSLP_UINT256_C_H so it never collides with the daemon's C++ + * `class uint256` (src/uint256.h, guard BITCOIN_UINT256_H). */ + +#ifndef ZSLP_UINT256_C_H +#define ZSLP_UINT256_C_H + +#include +#include +#include + +/* The reference compiles as C23, where `alignas` is a keyword. In the + * daemon's C build the C standard may be older (gnu11/gnu17), where + * `alignas` is the macro from . C++ has `alignas` as a + * keyword since C++11, so only the C path needs the header. */ +#if !defined(__cplusplus) && (__STDC_VERSION__ < 202000L) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct uint160 { + alignas(4) uint8_t data[20]; +}; + +struct uint256 { + alignas(4) uint8_t data[32]; +}; + +struct blob88 { + alignas(4) uint8_t data[11]; +}; + +static inline void uint160_set_null(struct uint160 *v) { memset(v->data, 0, 20); } +static inline void uint256_set_null(struct uint256 *v) { memset(v->data, 0, 32); } +static inline void blob88_set_null(struct blob88 *v) { memset(v->data, 0, 11); } + +static inline int uint160_is_null(const struct uint160 *v) +{ + for (int i = 0; i < 20; i++) + if (v->data[i] != 0) return 0; + return 1; +} + +static inline int uint256_is_null(const struct uint256 *v) +{ + for (int i = 0; i < 32; i++) + if (v->data[i] != 0) return 0; + return 1; +} + +static inline int uint160_cmp(const struct uint160 *a, const struct uint160 *b) +{ + return memcmp(a->data, b->data, 20); +} + +static inline int uint256_cmp(const struct uint256 *a, const struct uint256 *b) +{ + return memcmp(a->data, b->data, 32); +} + +static inline int uint256_eq(const struct uint256 *a, const struct uint256 *b) +{ + return memcmp(a->data, b->data, 32) == 0; +} + +static inline uint64_t uint256_get_cheap_hash(const struct uint256 *v) +{ + uint64_t result; + memcpy(&result, v->data, 8); + return result; +} + +#ifdef __cplusplus +} +#endif + +#endif /* ZSLP_UINT256_C_H */