diff --git a/src/libmw/include/mw/consensus/StealthSumValidator.h b/src/libmw/include/mw/consensus/StealthSumValidator.h index c164d3604..9d9f534eb 100644 --- a/src/libmw/include/mw/consensus/StealthSumValidator.h +++ b/src/libmw/include/mw/consensus/StealthSumValidator.h @@ -25,10 +25,11 @@ public: [](const Output& output) { return output.GetSenderPubKey(); } ); - std::transform( - body.GetInputs().cbegin(), body.GetInputs().cend(), std::back_inserter(lhs_keys), - [](const Input& input) { return input.GetInputPubKey(); } - ); + for (const Input& input : body.GetInputs()) { + if (!!input.GetInputPubKey()) { + lhs_keys.push_back(*input.GetInputPubKey()); + } + } PublicKey lhs_total; if (!lhs_keys.empty()) { diff --git a/src/libmw/include/mw/consensus/Weight.h b/src/libmw/include/mw/consensus/Weight.h index 75fc447cb..604e53c37 100644 --- a/src/libmw/include/mw/consensus/Weight.h +++ b/src/libmw/include/mw/consensus/Weight.h @@ -19,13 +19,20 @@ public: static constexpr size_t STANDARD_OUTPUT_WEIGHT = BASE_OUTPUT_WEIGHT + STANDARD_OUTPUT_FIELDS_WEIGHT; static constexpr size_t MAX_NUM_INPUTS = 50'000; - static constexpr size_t INPUT_BYTES = 195; + static constexpr size_t INPUT_BYTES = 196; static constexpr size_t MAX_MWEB_BYTES = 180 + (3 * 5) + // 180 bytes per header and 5 bytes each for input, output, and kernel vec size - (MAX_NUM_INPUTS * INPUT_BYTES) + // 50k inputs at 195 bytes each + (MAX_NUM_INPUTS * INPUT_BYTES) + // 50k inputs at 196 bytes each (ignoring extradata) (mw::MAX_BLOCK_WEIGHT * 60); // Ignoring inputs, no tx component is ever more than 60 bytes per unit of weight static size_t Calculate(const TxBody& tx_body) { + size_t input_weight = std::accumulate( + tx_body.GetInputs().begin(), tx_body.GetInputs().end(), (size_t)0, + [](size_t sum, const Input& input) { + return sum + CalcInputWeight(input.GetExtraData()); + } + ); + size_t kernel_weight = std::accumulate( tx_body.GetKernels().begin(), tx_body.GetKernels().end(), (size_t)0, [](size_t sum, const Kernel& kernel) { @@ -45,7 +52,7 @@ public: } ); - return kernel_weight + output_weight; + return input_weight + kernel_weight + output_weight; } static bool ExceedsMaximum(const TxBody& tx_body) @@ -53,6 +60,11 @@ public: return tx_body.GetInputs().size() > MAX_NUM_INPUTS || Calculate(tx_body) > mw::MAX_BLOCK_WEIGHT; } + static size_t CalcInputWeight(const std::vector& extra_data) + { + return ExtraBytesToWeight(extra_data.size()); + } + static size_t CalcKernelWeight( const bool has_stealth_excess, const CScript& pegout_script = CScript{}, diff --git a/src/libmw/include/mw/models/tx/Input.h b/src/libmw/include/mw/models/tx/Input.h index 6bac91c2a..5c137bc82 100644 --- a/src/libmw/include/mw/models/tx/Input.h +++ b/src/libmw/include/mw/models/tx/Input.h @@ -19,12 +19,18 @@ class Input : public Traits::IHashable, public Traits::ISerializable { + enum FeatureBit { + STEALTH_KEY_FEATURE_BIT = 0x01, + EXTRA_DATA_FEATURE_BIT = 0x02 + }; + public: // // Constructors // - Input(mw::Hash output_id, Commitment commitment, PublicKey input_pubkey, PublicKey output_pubkey, Signature signature) - : m_outputID(std::move(output_id)), + Input(uint8_t features, mw::Hash output_id, Commitment commitment, PublicKey input_pubkey, PublicKey output_pubkey, Signature signature) + : m_features(features), + m_outputID(std::move(output_id)), m_commitment(std::move(commitment)), m_inputPubKey(std::move(input_pubkey)), m_outputPubKey(std::move(output_pubkey)), @@ -57,10 +63,12 @@ public: // // Getters // + bool IsStandard() const noexcept { return m_features < FeatureBit::EXTRA_DATA_FEATURE_BIT; } const mw::Hash& GetOutputID() const noexcept { return m_outputID; } const Commitment& GetCommitment() const noexcept final { return m_commitment; } - const PublicKey& GetInputPubKey() const noexcept { return m_inputPubKey; } const PublicKey& GetOutputPubKey() const noexcept { return m_outputPubKey; } + const boost::optional& GetInputPubKey() const noexcept { return m_inputPubKey; } + const std::vector& GetExtraData() const noexcept { return m_extraData; } const Signature& GetSignature() const noexcept { return m_signature; } SignedMessage BuildSignedMsg() const noexcept; @@ -70,10 +78,22 @@ public: // IMPL_SERIALIZABLE(Input, obj) { + READWRITE(obj.m_features); READWRITE(obj.m_outputID); READWRITE(obj.m_commitment); - READWRITE(obj.m_inputPubKey); READWRITE(obj.m_outputPubKey); + + if (obj.m_features & STEALTH_KEY_FEATURE_BIT) { + PublicKey input_pubkey; + SER_WRITE(obj, input_pubkey = *obj.m_inputPubKey); + READWRITE(input_pubkey); + SER_READ(obj, obj.m_inputPubKey = boost::make_optional(std::move(input_pubkey))); + } + + if (obj.m_features & EXTRA_DATA_FEATURE_BIT) { + READWRITE(obj.m_extraData); + } + READWRITE(obj.m_signature); SER_READ(obj, obj.m_hash = Hashed(obj)); } @@ -84,6 +104,8 @@ public: const mw::Hash& GetHash() const noexcept final { return m_hash; } private: + uint8_t m_features; + // The ID of the output being spent. mw::Hash m_outputID; @@ -91,11 +113,13 @@ private: Commitment m_commitment; // The input pubkey. - PublicKey m_inputPubKey; + boost::optional m_inputPubKey; // The public key of the output being spent. PublicKey m_outputPubKey; + std::vector m_extraData; + Signature m_signature; mw::Hash m_hash; diff --git a/src/libmw/src/models/tx/Input.cpp b/src/libmw/src/models/tx/Input.cpp index 35e1ca45a..b09830053 100644 --- a/src/libmw/src/models/tx/Input.cpp +++ b/src/libmw/src/models/tx/Input.cpp @@ -2,8 +2,11 @@ #include #include +// Creates a standard input with a stealth key (feature bit = 1) Input Input::Create(const mw::Hash& output_id, const Commitment& commitment, const SecretKey& input_key, const SecretKey& output_key) { + uint8_t features = (uint8_t)FeatureBit::STEALTH_KEY_FEATURE_BIT; + PublicKey input_pubkey = PublicKey::From(input_key); PublicKey output_pubkey = PublicKey::From(output_key); @@ -18,26 +21,44 @@ Input Input::Create(const mw::Hash& output_id, const Commitment& commitment, con .Add(input_key) .Total(); + // Hash message + Hasher msg_hasher; + msg_hasher << features << output_id; + mw::Hash msg_hash = msg_hasher.hash(); + return Input( + features, output_id, commitment, std::move(input_pubkey), std::move(output_pubkey), - Schnorr::Sign(sig_key.data(), output_id) + Schnorr::Sign(sig_key.data(), msg_hash) ); } SignedMessage Input::BuildSignedMsg() const noexcept { - // Hash keys (K_i||K_o) - Hasher key_hasher; - key_hasher << GetInputPubKey() << GetOutputPubKey(); - SecretKey key_hash = key_hasher.hash(); + // Calculate message hash + Hasher msg_hasher; + msg_hasher << m_features << GetOutputID(); + if (m_features & EXTRA_DATA_FEATURE_BIT) { + msg_hasher << GetExtraData(); + } + mw::Hash message = msg_hasher.hash(); - // Calculate aggregated key K_agg = K_i + HASH(K_i||K_o) * K_o - PublicKey public_key = GetOutputPubKey() - .Mul(key_hash) - .Add(GetInputPubKey()); + // Calculate public key + PublicKey public_key = GetOutputPubKey(); + if (m_features & STEALTH_KEY_FEATURE_BIT) { + // Hash keys (K_i||K_o) + Hasher key_hasher; + key_hasher << (*GetInputPubKey()) << GetOutputPubKey(); + SecretKey key_hash = key_hasher.hash(); - return SignedMessage{GetOutputID(), public_key, GetSignature()}; + // Calculate aggregated key K_agg = K_i + HASH(K_i||K_o) * K_o + public_key = public_key + .Mul(key_hash) + .Add(*GetInputPubKey()); + } + + return SignedMessage{message, public_key, GetSignature()}; } \ No newline at end of file diff --git a/src/libmw/src/models/tx/Transaction.cpp b/src/libmw/src/models/tx/Transaction.cpp index 06e878539..adacc08f7 100644 --- a/src/libmw/src/models/tx/Transaction.cpp +++ b/src/libmw/src/models/tx/Transaction.cpp @@ -28,6 +28,12 @@ Transaction::CPtr Transaction::Create( bool Transaction::IsStandard() const noexcept { + for (const Input& input : GetInputs()) { + if (!input.IsStandard()) { + return false; + } + } + for (const Kernel& kernel : GetKernels()) { if (!kernel.IsStandard()) { return false; diff --git a/src/libmw/test/tests/consensus/Test_StealthSumValidator.cpp b/src/libmw/test/tests/consensus/Test_StealthSumValidator.cpp index af8194474..3d2f6d346 100644 --- a/src/libmw/test/tests/consensus/Test_StealthSumValidator.cpp +++ b/src/libmw/test/tests/consensus/Test_StealthSumValidator.cpp @@ -25,6 +25,7 @@ BOOST_AUTO_TEST_CASE(ValidateStealthSum) SecretKey input1_key = SecretKey::Random(); SecretKey input1_output_key = SecretKey::Random(); Input input1( + 1, // features SecretKey::Random().GetBigInt(), // output_id Commitment::Random(), // commitment PublicKey::From(input1_key), // input pubkey @@ -36,6 +37,7 @@ BOOST_AUTO_TEST_CASE(ValidateStealthSum) SecretKey input2_key = SecretKey::Random(); SecretKey input2_output_key = SecretKey::Random(); Input input2( + 1, // features SecretKey::Random().GetBigInt(), // output_id Commitment::Random(), // commitment PublicKey::From(input2_key), // input pubkey diff --git a/src/libmw/test/tests/models/tx/Test_Input.cpp b/src/libmw/test/tests/models/tx/Test_Input.cpp index dbea82577..5412e3485 100644 --- a/src/libmw/test/tests/models/tx/Test_Input.cpp +++ b/src/libmw/test/tests/models/tx/Test_Input.cpp @@ -16,7 +16,7 @@ BOOST_AUTO_TEST_CASE(PlainTxInput) PublicKey input_pubkey = PublicKey::Random(); PublicKey output_pubkey = PublicKey::Random(); Signature signature(SecretKey64::Random().GetBigInt()); - Input input(output_id, commit, input_pubkey, output_pubkey, signature); + Input input(1, output_id, commit, input_pubkey, output_pubkey, signature); // // Serialization @@ -25,6 +25,10 @@ BOOST_AUTO_TEST_CASE(PlainTxInput) std::vector serialized = input.Serialized(); CDataStream deserializer(serialized, SER_DISK, PROTOCOL_VERSION); + uint8_t features; + deserializer >> features; + BOOST_REQUIRE(features == 1); + mw::Hash output_id2; deserializer >> output_id2; BOOST_REQUIRE(output_id2 == output_id); @@ -33,14 +37,14 @@ BOOST_AUTO_TEST_CASE(PlainTxInput) deserializer >> commit2; BOOST_REQUIRE(commit2 == commit); - PublicKey input_pubkey2; - deserializer >> input_pubkey2; - BOOST_REQUIRE(input_pubkey2 == input_pubkey); - PublicKey output_pubkey2; deserializer >> output_pubkey2; BOOST_REQUIRE(output_pubkey2 == output_pubkey); + PublicKey input_pubkey2; + deserializer >> input_pubkey2; + BOOST_REQUIRE(input_pubkey2 == input_pubkey); + Signature signature2; deserializer >> signature2; BOOST_REQUIRE(signature2 == signature); @@ -54,10 +58,12 @@ BOOST_AUTO_TEST_CASE(PlainTxInput) { BOOST_REQUIRE(input.GetOutputID() == output_id); BOOST_REQUIRE(input.GetCommitment() == commit); - BOOST_REQUIRE(input.GetInputPubKey() == input_pubkey); + BOOST_REQUIRE(*input.GetInputPubKey() == input_pubkey); BOOST_REQUIRE(input.GetOutputPubKey() == output_pubkey); BOOST_REQUIRE(input.GetSignature() == signature); } + + // MW: TODO - Input version tests } BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file