diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index ed96ae1b575..b1489d616fd 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -26,6 +26,9 @@ Result TransactionSigner::sign() { std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); + if (plan.utxos.size() == 0) { + return Result::failure("Plan without UTXOs"); + } const auto hashSingle = hashTypeIsSingle(static_cast(input.hash_type())); for (auto i = 0; i < plan.utxos.size(); i += 1) { auto& utxo = plan.utxos[i]; diff --git a/src/Bitcoin/TransactionSigner.h b/src/Bitcoin/TransactionSigner.h index 972bca1eb46..f6f3f292eb3 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -61,13 +61,15 @@ class TransactionSigner { /// \returns the signed transaction or an error. Result sign(); + // internal, public for testability and Decred + static Data pushAll(const std::vector& results); + private: Result sign(Script script, size_t index, const Proto::UnspentTransaction& utxo); Result> signStep(Script script, size_t index, const Proto::UnspentTransaction& utxo, uint32_t version); Data createSignature(const Transaction& transaction, const Script& script, const Data& key, size_t index, Amount amount, uint32_t version); - Data pushAll(const std::vector& results); /// Returns the private key for the given public key hash. Data keyForPublicKeyHash(const Data& hash) const; diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index ec089551258..366bf43844f 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -9,6 +9,7 @@ #include "TransactionInput.h" #include "TransactionOutput.h" #include "../Bitcoin/SigHashType.h" +#include "../Bitcoin/TransactionSigner.h" #include "../BinaryCoding.h" #include "../Hash.h" @@ -50,6 +51,9 @@ Result Signer::sign() { std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), std::back_inserter(signedInputs)); + if (txPlan.utxos.size() == 0) { + return Result::failure("Plan without UTXOs"); + } const auto hashSingle = Bitcoin::hashTypeIsSingle(static_cast(input.hash_type())); for (auto i = 0; i < txPlan.utxos.size(); i += 1) { auto& utxo = txPlan.utxos[i]; @@ -96,7 +100,7 @@ Result Signer::sign(Bitcoin::Script script, size_t index) { results.push_back(redeemScript.bytes); } - return Result::success(Bitcoin::Script(pushAll(results))); + return Result::success(Bitcoin::Script(Bitcoin::TransactionSigner::pushAll(results))); } Result> Signer::signStep(Bitcoin::Script script, size_t index) { @@ -182,30 +186,6 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri return signature; } -Data Signer::pushAll(const std::vector& results) { - auto data = Data{}; - for (auto& result : results) { - if (result.empty()) { - data.push_back(OP_0); - } else if (result.size() == 1 && result[0] >= 1 && result[0] <= 16) { - data.push_back(Bitcoin::Script::encodeNumber(result[0])); - } else if (result.size() < OP_PUSHDATA1) { - data.push_back(static_cast(result.size())); - } else if (result.size() <= 0xff) { - data.push_back(OP_PUSHDATA1); - data.push_back(static_cast(result.size())); - } else if (result.size() <= 0xffff) { - data.push_back(OP_PUSHDATA2); - encode16LE(static_cast(result.size()), data); - } else { - data.push_back(OP_PUSHDATA4); - encode32LE(static_cast(result.size()), data); - } - std::copy(begin(result), end(result), back_inserter(data)); - } - return data; -} - Data Signer::keyForPublicKeyHash(const Data& hash) const { for (auto& key : input.private_key()) { auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); diff --git a/src/Decred/Signer.h b/src/Decred/Signer.h index 4d01bf512c7..a6ee7da31b1 100644 --- a/src/Decred/Signer.h +++ b/src/Decred/Signer.h @@ -76,7 +76,6 @@ class Signer { Result> signStep(Bitcoin::Script script, size_t index); Data createSignature(const Transaction& transaction, const Bitcoin::Script& script, const Data& key, size_t index); - Data pushAll(const std::vector& results); /// Returns the private key for the given public key hash. Data keyForPublicKeyHash(const Data& hash) const; diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index 3804d380049..e294177e4ab 100644 --- a/src/Decred/TransactionBuilder.h +++ b/src/Decred/TransactionBuilder.h @@ -48,7 +48,6 @@ struct TransactionBuilder { auto input = TransactionInput(); input.previousOutput = utxo.out_point(); input.sequence = utxo.out_point().sequence(); - input.sequence = utxo.out_point().sequence(); tx.inputs.push_back(std::move(input)); } diff --git a/tests/Aion/TWAnySignerTests.cpp b/tests/Aion/TWAnySignerTests.cpp index 9ba8cc58fac..5abc8d729cd 100644 --- a/tests/Aion/TWAnySignerTests.cpp +++ b/tests/Aion/TWAnySignerTests.cpp @@ -25,7 +25,7 @@ TEST(TWAnySignerAion, Sign) { auto gasPrice = store(uint256_t(20000000000)); input.set_gas_price(gasPrice.data(), gasPrice.size()); auto gasLimit = store(uint256_t(21000)); - input.set_gas_limit(gasLimit.data(), gasLimit.size());; + input.set_gas_limit(gasLimit.data(), gasLimit.size()); auto nonce = store(uint256_t(9)); input.set_nonce(nonce.data(), nonce.size()); input.set_private_key(privateKey.data(), privateKey.size()); diff --git a/tests/Bitcoin/BitcoinScriptTests.cpp b/tests/Bitcoin/BitcoinScriptTests.cpp index c54505e9894..4942cc948ff 100644 --- a/tests/Bitcoin/BitcoinScriptTests.cpp +++ b/tests/Bitcoin/BitcoinScriptTests.cpp @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. #include "Bitcoin/Script.h" +#include "Bitcoin/TransactionSigner.h" #include "../interface/TWTestUtilities.h" #include "HexCoding.h" @@ -283,3 +284,49 @@ TEST(BitcoinScript, MatchMultiSig) { EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); EXPECT_EQ(hex(keys[2]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); } + +TEST(BitcoinTransactionSigner, PushAllEmpty) { + { + std::vector input = {}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), ""); + } + { + std::vector input = {parse_hex("")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "00"); + } + { + std::vector input = {parse_hex("09")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "59" "09"); + } + { + std::vector input = {parse_hex("00010203040506070809")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "0a" "00010203040506070809"); + } + { + std::vector input = {parse_hex("0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809")}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), "4c50" "0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809"); + } + { + // 2-byte len + Data in1 = Data(256 + 10); + Data expected = parse_hex("4d" "0a01"); + TW::append(expected, in1); + std::vector input = {in1}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), hex(expected)); + } + { + // 4-byte len + Data in1 = Data(65536 + 256 + 10); + Data expected = parse_hex("4e" "0a010100"); + TW::append(expected, in1); + std::vector input = {in1}; + Data res = TransactionSigner::pushAll(input); + EXPECT_EQ(hex(res), hex(expected)); + } +} \ No newline at end of file diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp index f7b5a08d1b6..b3d7e447c32 100644 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/Bitcoin/TWBitcoinSigningTests.cpp @@ -25,8 +25,68 @@ using namespace TW; using namespace TW::Bitcoin; +TEST(BitcoinSigning, SignP2PKH) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + ASSERT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + + auto utxo0Script = Script::buildPayToPublicKeyHash(utxoPubkeyHash); + Data scriptHash; + utxo0Script.matchPayToPublicKeyHash(scriptHash); + ASSERT_EQ(hex(scriptHash), "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + + auto utxo0 = input.add_utxo(); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(625'000'000); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + auto utxo1 = input.add_utxo(); + auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); + utxo1->set_amount(600'000'000); + utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); + utxo1->mutable_out_point()->set_index(1); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + ASSERT_TRUE(result) << result.error(); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(true, serialized); + ASSERT_EQ(hex(serialized), + "01000000" + "0001" + "01" + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "6a47304402202819d70d4bec472113a1392cadc0860a7a1b34ea0869abb4bdce3290c3aba086022023eff75f410ad19cdbe6c6a017362bd554ce5fb906c13534ddc306be117ad30a012103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ffffffff" + "02" + "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "aefd3c1100000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "0000000000" + ); +} + TEST(BitcoinSigning, EncodeP2WPKH) { - auto emptyScript = WRAP(TWBitcoinScript, TWBitcoinScriptCreate()); auto unsignedTx = Transaction(1, 0x11); auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); @@ -94,16 +154,16 @@ TEST(BitcoinSigning, SignP2WPKH) { auto utxo1 = input.add_utxo(); auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); utxo1->set_amount(600'000'000); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "03b30d55430f08365d19a62d3bd32e459ab50984fbcf22921ecc85f1e09dc6ed" @@ -161,16 +221,16 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { auto utxo1 = input.add_utxo(); auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); utxo1->set_amount(210'000'000); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; @@ -180,11 +240,13 @@ TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { "0001" "02" "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49483045022100fd8591c3611a07b55f509ec850534c7a9c49713c9b8fa0e844ea06c2e65e19d702205e3806676192e790bc93dd4c28e937c4bf97b15f189158ba1a30d7ecff5ee75503" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "49483045022100c723312dccfcc1f3716ae1fc8b045dda97a6f381cadad99a11289b73d7ce89390220261e7a75b8ccef3cddc16ab2f5a3cd7c47626a0c3a6e35f4bcf1b31235c9e73403" "00000000" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "0100000000" "ffffffff" "02" "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "000000000000" + "0002" + "47304402206b91d2c69022a54652731b4302eabe59c87949cf62f4c5674c7d4c0d1fbf898102200cee8eeb6ef9542426788c06ed51004799b730083ae3d4daf3c3d5fdc2275d1d0321025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" ); } @@ -226,16 +288,16 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { auto utxo1 = input.add_utxo(); auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo0Script.data(), utxo0Script.size()); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); utxo1->set_amount(210'000'000); utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); utxo1->mutable_out_point()->set_index(1); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; @@ -245,11 +307,13 @@ TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { "0001" "02" "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "4847304402206ed3e388d440cb845eef2fce0740b83bdd77764ad0e7dd815a20760718291a5302203f78d743350d80aa2508e90d5a984636c5503d02c1e8656442f0f0275db95baa80" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "4847304402202cb0d71911596b9527b68829689fe600fdd237fa890826f2fbaf61a43d2a945f022038102d595d27e60a75c396388cb5d8c1d0bd341e0040dba56fbad413c3395bf080" "00000000" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "ffffffff" "02" "b0bf031400000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" "daef040500000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "000000000000" + "0002" + "483045022100a5eedab7da09317141e35730256ef9b76da0c2442995a1c2b5458ee7d8834ba302201dc10b47cd4e2e53c7253770cd6907c94c828317d217e3065db009345acf41ac8021025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" ); } @@ -296,8 +360,8 @@ Proto::SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType) { auto p2wsh = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); utxo0->set_script(p2wsh.bytes.data(), p2wsh.bytes.size()); utxo0->set_amount(1226); - auto hash0 = DATA("0001000000000000000000000000000000000000000000000000000000000000"); - utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); + auto hash0 = parse_hex("0001000000000000000000000000000000000000000000000000000000000000"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); utxo0->mutable_out_point()->set_index(0); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); return input; @@ -310,7 +374,7 @@ TEST(BitcoinSigning, SignP2WSH) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -338,7 +402,7 @@ TEST(BitcoinSigning, SignP2WSH_HashNone) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -366,7 +430,7 @@ TEST(BitcoinSigning, SignP2WSH_HashSingle) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -394,7 +458,7 @@ TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { // Sign auto result = TransactionSigner(std::move(input)).sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); // txid = "b588f910d7ff03d5fbc3da91f62e48bab47153229c8d1b114b43cb31b9c4d0dd" @@ -569,7 +633,7 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { signer.transaction = unsignedTx; signer.plan.utxos = {*utxo}; auto result = signer.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); auto expected = "" @@ -602,3 +666,32 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { signedTx.encode(true, serialized); ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); } + +TEST(BitcoinSigning, Sign_NegativeNoUtxos) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + // Sign + auto result = TransactionSigner(std::move(input)).sign(); + + // Fails as there are 0 utxos + ASSERT_FALSE(result) << result.error(); +} diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/BitcoinGold/TWBitcoinGoldTests.cpp index 1d5c2bf84f9..0c209135b7a 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/BitcoinGold/TWBitcoinGoldTests.cpp @@ -103,7 +103,7 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { txSinger.transaction.lockTime = 0x00098971; auto result = txSinger.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/BitcoinGold/TWSignerTests.cpp index 5e91212b883..9a5c587c66c 100644 --- a/tests/BitcoinGold/TWSignerTests.cpp +++ b/tests/BitcoinGold/TWSignerTests.cpp @@ -62,7 +62,7 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { txSinger.transaction.lockTime = 0x00098971; auto result = txSinger.sign(); - ASSERT_TRUE(result) << result.error();; + ASSERT_TRUE(result) << result.error(); auto signedTx = result.payload(); Data serialized; diff --git a/tests/Decred/SignerTests.cpp b/tests/Decred/SignerTests.cpp index f2889377aab..a49067965ac 100644 --- a/tests/Decred/SignerTests.cpp +++ b/tests/Decred/SignerTests.cpp @@ -6,7 +6,6 @@ #include "Decred/Address.h" #include "Decred/Signer.h" -#include "proto/Bitcoin.pb.h" #include "proto/Decred.pb.h" #include "Hash.h" @@ -19,7 +18,7 @@ using namespace TW; using namespace TW::Decred; -TEST(DecredSigner, Sign) { +TEST(DecredSigner, SignP2PKH) { const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -62,7 +61,7 @@ TEST(DecredSigner, Sign) { auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); utxo0->set_amount(100'000'000); - utxo0->mutable_out_point()->set_hash(originTx.hash().data(), 32); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); utxo0->mutable_out_point()->set_index(0); @@ -114,3 +113,256 @@ TEST(DecredSigner, Sign) { result.payload().encode(encoded); EXPECT_EQ(hex(encoded), expectedEncoded); } + +TEST(DecredSigner, SignP2SH) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[hex(scriptHash.begin(), scriptHash.end())] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToScriptHash(scriptHash); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(100'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + + // Sign + auto signer = Signer(std::move(input)); + signer.transaction = redeemTx; + signer.txPlan.utxos.push_back(*utxo0); + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + + const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100" // Serialize type + "0000" // Version + "01" // Inputs + "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash + "00000000" // Index + "00" // Tree + "ffffffff" // Sequence + "01" // Outputs + "0000000000000000" // Value + "0000" // Version + "00" // Script + "00000000" // Lock time + "00000000" // Expiry + "01" + "00e1f50500000000" // Value + "00000000" // Block height + "ffffffff" // Block index + "9e47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} + +TEST(DecredSigner, SignNegativeNoUtxo) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + // Sign + auto signer = Signer(std::move(input)); + signer.transaction = redeemTx; + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + // Fails as there are 0 utxos + ASSERT_FALSE(result); +} + +TEST(DecredSigner, SignP2PKH_Noplan) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 150'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(150'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + + // Sign + auto signer = Signer(std::move(input)); + signer.transaction = redeemTx; + //signer.txPlan.utxos.push_back(*utxo0); + //signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + std::cerr << "result.payload().inputs " << result.payload().inputs.size() << "\n"; + ASSERT_TRUE(result.payload().inputs.size() >= 1); + + const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f0121" + "02a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100" // Serialize type + "0000" // Version + "01" // Inputs + "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash + "00000000" // Index + "00" // Tree + "ffffffff" // Sequence + "01" // Outputs + "0000000000000000" // Value + "0000" // Version + "00" // Script + "00000000" // Lock time + "00000000" // Expiry + "01" + "00e1f50500000000" // Value + "00000000" // Block height + "ffffffff" // Block index + "6a47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +}