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 e5ba42006fe..9617cd2fcbb 100644 --- a/src/Nimiq/Transaction.cpp +++ b/src/Nimiq/Transaction.cpp @@ -9,25 +9,33 @@ namespace TW::Nimiq { -const uint8_t NETWORK_ID = 42; 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 + 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 @@ -40,10 +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); + 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; +} + +uint8_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..98ea799387d 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; + uint8_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/swift/Tests/Blockchains/NimiqTests.swift b/swift/Tests/Blockchains/NimiqTests.swift index 8ee8dac51ed..d96ad33e5ce 100644 --- a/swift/Tests/Blockchains/NimiqTests.swift +++ b/swift/Tests/Blockchains/NimiqTests.swift @@ -14,10 +14,11 @@ class NimiqTests: XCTestCase { $0.value = 42042042 $0.fee = 1000 $0.validityStartHeight = 314159 + $0.networkID = .mainnetAlbatross } let output: NimiqSigningOutput = AnySigner.sign(input: input, coin: .nimiq) - XCTAssertEqual(output.encoded.hexString, "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b") + XCTAssertEqual(output.encoded.hexString, "000070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f180ba678744be3bf9cd44fbcdabfb5be209f21739934836e26055610ab02720fa99489219d9f3581664473a1b40b30ad1f6e13150d59f8234a42c3f0de3d505405") } } diff --git a/tests/chains/Nimiq/SignerTests.cpp b/tests/chains/Nimiq/SignerTests.cpp index c22441f2f39..d8a56bdb9cd 100644 --- a/tests/chains/Nimiq/SignerTests.cpp +++ b/tests/chains/Nimiq/SignerTests.cpp @@ -30,7 +30,8 @@ TEST(NimiqSigner, Sign) { Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), 42042042, 1000, - 314159 + 314159, + Proto::NetworkId::Mainnet ); Signer signer; diff --git a/tests/chains/Nimiq/TWAnySignerTests.cpp b/tests/chains/Nimiq/TWAnySignerTests.cpp index a618b1ac04f..f8879960cd1 100644 --- a/tests/chains/Nimiq/TWAnySignerTests.cpp +++ b/tests/chains/Nimiq/TWAnySignerTests.cpp @@ -28,4 +28,22 @@ TEST(TWAnySignerNimiq, Sign) { 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"); +} + } // namespace TW::Nimiq::tests diff --git a/tests/chains/Nimiq/TransactionTests.cpp b/tests/chains/Nimiq/TransactionTests.cpp index 679f955f4ca..cd6eaeb2b22 100644 --- a/tests/chains/Nimiq/TransactionTests.cpp +++ b/tests/chains/Nimiq/TransactionTests.cpp @@ -22,7 +22,8 @@ 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()), "000082d5f776378ccbe34a3d941f22d4715bc9f81e0d001450ffc385cd4e7c6ac9a7e91614ca67ff90568a0000000000028182ba00000000000003e80004cb2f2a00"); @@ -39,7 +40,8 @@ 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("74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b");