diff --git a/src/Nimiq/Signer.cpp b/src/Nimiq/Signer.cpp index ed08cefdff3..e8de7375c62 100644 --- a/src/Nimiq/Signer.cpp +++ b/src/Nimiq/Signer.cpp @@ -19,7 +19,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { /* destination */Address(input.destination()), /* amount */input.value(), /* fee */input.fee(), - /* vsh */input.validity_start_height() + /* vsh */input.validity_start_height(), + /* networkId */input.network_id() ); auto signer = Signer(); diff --git a/src/Nimiq/Transaction.cpp b/src/Nimiq/Transaction.cpp index 9a832240ce6..c5baffbda80 100644 --- a/src/Nimiq/Transaction.cpp +++ b/src/Nimiq/Transaction.cpp @@ -9,26 +9,33 @@ namespace TW::Nimiq { -const uint8_t NETWORK_ID = 24; const uint8_t EMPTY_FLAGS = 0; std::vector Transaction::serialize() const { + // Source code: + // https://github.com/nimiq/core-rs-albatross/blame/b8ed402c9096ffb54afea52347b91ab7831e75de/primitives/transaction/src/lib.rs#L699 + std::vector data; data.push_back(0x00); // Basic TX type - data.push_back(0x00); // Signature Proof type and flags (Ed25519 type and no flags) + if (isAlbatross()) { + data.push_back(0x00); // Signature Proof type and flags (Ed25519 type and no flags) + } data.insert(data.end(), sender_pub_key.begin(), sender_pub_key.end()); data.insert(data.end(), destination.bytes.begin(), destination.bytes.end()); encode64BE(amount, data); encode64BE(fee, data); encode32BE(vsh, data); - data.push_back(NETWORK_ID); + data.push_back(consensusNetworkId()); data.insert(data.end(), signature.begin(), signature.end()); return data; } std::vector Transaction::getPreImage() const { + // Source code: + // https://github.com/nimiq/core-rs-albatross/blame/b8ed402c9096ffb54afea52347b91ab7831e75de/primitives/transaction/src/lib.rs#L582 + std::vector data; // Build pre-image @@ -41,11 +48,32 @@ std::vector Transaction::getPreImage() const { encode64BE(amount, data); encode64BE(fee, data); encode32BE(vsh, data); - data.push_back(NETWORK_ID); + data.push_back(consensusNetworkId()); data.push_back(EMPTY_FLAGS); - data.push_back(0x00); // Sender Data size (+ 0 bytes of data) + if (isAlbatross()) { + data.push_back(0x00); // Sender Data size (+ 0 bytes of data) + } return data; } +bool Transaction::isAlbatross() const { + if (networkId == Proto::NetworkId::MainnetAlbatross) { + return true; + } + return false; +} + +uint32_t Transaction::consensusNetworkId() const { + switch (networkId) { + case Proto::NetworkId::UseDefault: + case Proto::NetworkId::Mainnet: + return static_cast(Proto::NetworkId::Mainnet); + case Proto::NetworkId::MainnetAlbatross: + return static_cast(Proto::NetworkId::MainnetAlbatross); + default: + throw std::invalid_argument("Invalid network ID"); + } +} + } // namespace TW::Nimiq diff --git a/src/Nimiq/Transaction.h b/src/Nimiq/Transaction.h index 4615d440537..adcd6d65325 100644 --- a/src/Nimiq/Transaction.h +++ b/src/Nimiq/Transaction.h @@ -5,6 +5,7 @@ #pragma once #include "Address.h" +#include "proto/Nimiq.pb.h" namespace TW::Nimiq { @@ -20,16 +21,22 @@ class Transaction { uint64_t fee; // Validity start (block) height uint32_t vsh; + // Network ID + Proto::NetworkId networkId; // Sender signature std::array signature; Transaction(const std::array& sender, const Address& dest, uint64_t amount, - uint64_t fee, uint32_t vsh) - : sender_pub_key(sender), destination(dest), amount(amount), fee(fee), vsh(vsh) {} + uint64_t fee, uint32_t vsh, Proto::NetworkId networkId) + : sender_pub_key(sender), destination(dest), amount(amount), fee(fee), vsh(vsh), networkId(networkId) {} public: std::vector serialize() const; std::vector getPreImage() const; + + private: + bool isAlbatross() const; + uint32_t consensusNetworkId() const; }; } // namespace TW::Nimiq diff --git a/src/proto/Nimiq.proto b/src/proto/Nimiq.proto index 76d00dd903e..13abbd9c734 100644 --- a/src/proto/Nimiq.proto +++ b/src/proto/Nimiq.proto @@ -3,6 +3,14 @@ syntax = "proto3"; package TW.Nimiq.Proto; option java_package = "wallet.core.jni.proto"; +enum NetworkId { + UseDefault = 0; + // Default PoW Mainnet. + Mainnet = 42; + // PoS Mainnet starting at the PoW block height 3’456’000. + MainnetAlbatross = 24; +} + // Input data necessary to create a signed transaction. message SigningInput { // The secret private key used for signing (32 bytes). @@ -19,6 +27,9 @@ message SigningInput { // Validity start, in block height uint32 validity_start_height = 5; + + // Network ID. + NetworkId network_id = 6; } // Result containing the signed and encoded transaction. diff --git a/tests/chains/Nimiq/SignerTests.cpp b/tests/chains/Nimiq/SignerTests.cpp index e29208e10ec..d8a56bdb9cd 100644 --- a/tests/chains/Nimiq/SignerTests.cpp +++ b/tests/chains/Nimiq/SignerTests.cpp @@ -30,14 +30,15 @@ TEST(NimiqSigner, Sign) { Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), 42042042, 1000, - 314159 + 314159, + Proto::NetworkId::Mainnet ); Signer signer; signer.sign(privateKey, tx); ASSERT_EQ(hex(tx.signature), - "0ba678744be3bf9cd44fbcdabfb5be209f21739934836e26055610ab02720fa99489219d9f3581664473a1b40b30ad1f6e13150d59f8234a42c3f0de3d505405"); + "74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); } } // namespace TW::Nimiq diff --git a/tests/chains/Nimiq/TWAnySignerTests.cpp b/tests/chains/Nimiq/TWAnySignerTests.cpp index 3d0cb3d9327..f8879960cd1 100644 --- a/tests/chains/Nimiq/TWAnySignerTests.cpp +++ b/tests/chains/Nimiq/TWAnySignerTests.cpp @@ -25,6 +25,24 @@ TEST(TWAnySignerNimiq, Sign) { Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeNimiq); + EXPECT_EQ(hex(output.encoded()), "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); +} + +TEST(TWAnySignerNimiq, SignPoS) { + auto privateKey = parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"); + + Proto::SigningInput input; + + input.set_destination("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"); + input.set_fee(1000); + input.set_value(42042042); + input.set_validity_start_height(314159); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_network_id(Proto::NetworkId::MainnetAlbatross); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNimiq); + EXPECT_EQ(hex(output.encoded()), "000070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f180ba678744be3bf9cd44fbcdabfb5be209f21739934836e26055610ab02720fa99489219d9f3581664473a1b40b30ad1f6e13150d59f8234a42c3f0de3d505405"); } diff --git a/tests/chains/Nimiq/TransactionTests.cpp b/tests/chains/Nimiq/TransactionTests.cpp index 90e6381a0e2..cd6eaeb2b22 100644 --- a/tests/chains/Nimiq/TransactionTests.cpp +++ b/tests/chains/Nimiq/TransactionTests.cpp @@ -22,10 +22,11 @@ TEST(NimiqTransaction, PreImage) { Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), 42042042, 1000, - 314159 + 314159, + Proto::NetworkId::UseDefault ); ASSERT_EQ(hex(tx.getPreImage()), - "000082d5f776378ccbe34a3d941f22d4715bc9f81e0d001450ffc385cd4e7c6ac9a7e91614ca67ff90568a0000000000028182ba00000000000003e80004cb2f180000"); + "000082d5f776378ccbe34a3d941f22d4715bc9f81e0d001450ffc385cd4e7c6ac9a7e91614ca67ff90568a0000000000028182ba00000000000003e80004cb2f2a00"); } TEST(NimiqTransaction, Serialize) { @@ -39,14 +40,15 @@ TEST(NimiqTransaction, Serialize) { Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), 42042042, 1000, - 314159 + 314159, + Proto::NetworkId::Mainnet ); - const auto signature = parse_hex("0ba678744be3bf9cd44fbcdabfb5be209f21739934836e26055610ab02720fa99489219d9f3581664473a1b40b30ad1f6e13150d59f8234a42c3f0de3d505405"); + const auto signature = parse_hex("74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); std::copy(signature.begin(), signature.end(), tx.signature.begin()); ASSERT_EQ(hex(tx.serialize()), - "000070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f180ba678744be3bf9cd44fbcdabfb5be209f21739934836e26055610ab02720fa99489219d9f3581664473a1b40b30ad1f6e13150d59f8234a42c3f0de3d505405"); + "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); } } // namespace TW::Nimiq