dart-bitcoin / bitcoin_flutter

A dart Bitcoin library for Flutter.
MIT License
165 stars 123 forks source link

Building a transaction with multiple P2PKH inputs and Single P2WPKH and 2 outputs #39

Open kaning opened 4 years ago

kaning commented 4 years ago

Given this payload of transaction data

"unspentAddressOutputs": {
     "8db82fe48d275647df6e01246978594dffcf8166d080a58868be51ed63dc94cf": [
       {
         "value": 100000,
         "script": "76a9142b9bc14f739fbab340142f9e1ec800ff654e15d188ac",
         "addr": "mjVXwMisfn1hv7pb1a6BuU8aaBWmyNtT3G",
         "spent": false,
         "type": 0,
         "n": 1
       }
     ],
     "b8a654afc77292d2011c7ae79d1e2ef469add829d2f1ff4ec3d287216da9d2c1": [
       {
         "value": 1006,
         "script": "76a9145c654954c781d522d3d965d78bc7ff5fb983b5a888ac",
         "addr": "mowVoD35wRZWSueDN1UPDSdzVHZLh22gj7",
         "spent": false,
         "type": 0,
         "n": 0
       },
       {
         "value": 98736,
         "script": "00142b9bc14f739fbab340142f9e1ec800ff654e15d1",
         "spent": false,
         "type": 0,
         "n": 1
       }
     ],
}

I am trying to construct a transaction using these unspent outputs but it fails on building the transaction

final TransactionBuilder txb = TransactionBuilder(network: wallet.network);
txb.addInput('8db82fe48d275647df6e01246978594dffcf8166d080a58868be51ed63dc94cf', 1);
txb.addInput('b8a654afc77292d2011c7ae79d1e2ef469add829d2f1ff4ec3d287216da9d2c1', 0);
txb.addInput('b8a654afc77292d2011c7ae79d1e2ef469add829d2f1ff4ec3d287216da9d2c1', 1); //<- think this one is P2WPKH

// total is 199742
txb.addOutput('mowVoD35wRZWSueDN1UPDSdzVHZLh22gj7', 109742);
txb.addOutput('mjVXwMisfn1hv7pb1a6BuU8aaBWmyNtT3G', 90000 - fee);

txb.sign(vin: 0, keyPair: keypair);
txb.sign(vin: 1, keyPair: keypair);
txb.sign(vin: 2, keyPair: keypair, witnessValue: 98736);

txb.build.toHex() // this throws exception 'length called on null' and that comes from transaction.dart:253

when I remove

txb.addInput('b8a654afc77292d2011c7ae79d1e2ef469add829d2f1ff4ec3d287216da9d2c1', 1); //<- think this one is P2WPKH

and just sign

txb.sign(vin: 0, keyPair: keypair);
txb.sign(vin: 1, keyPair: keypair);

I get

0100000002cf94dc63ed51be6888a580d06681cfff4d59786924016edf4756278de42fb88d010000006a473044022067fe36a90b583006c491bb4ae22b6112ebf01d98095f71c18c6f21fc4e148edc02202a4553192d3c4f17181bce74d1be5692fb37d3b0757002f3286baf8902861ce3012102b2ed1998d523265ee7b28772ac5f43753cf83ceb5cb624a8cc136830745b26b1ffffffffc1d2a96d2187d2c34efff1d229d8ad69f42e1e9de77a1c01d29272c7af54a6b8000000006b483045022100f610387f519a51e6312ce015264f896f240c5dbf3e43a2fc377c80320c510835022048ff8890ae6350121a7ee0b1f6fc159dd413b2e0b0b11cabbab7e2f79eb02d02012102b2ed1998d523265ee7b28772ac5f43753cf83ceb5cb624a8cc136830745b26b1ffffffff024c270000000000001976a9145c654954c781d522d3d965d78bc7ff5fb983b5a888acea5d0100000000001976a9142b9bc14f739fbab340142f9e1ec800ff654e15d188ac00000000

basically equal to Script failed an OP_EQUALVERIFY operation

Am I using the library wrong?

longhoangwkm commented 4 years ago

Thanks for using @kaning, I will check it soon

SFzxc commented 4 years ago
txb.addInput('b8a654afc77292d2011c7ae79d1e2ef469add829d2f1ff4ec3d287216da9d2c1', 1); //<- think this one is P2WPKH

It correct. But P2WPKH utxo need to add in a different way. You can take a look here: https://github.com/dart-bitcoin/bitcoin_flutter/blob/master/test/integration/transactions_test.dart#L73-L78

SFzxc commented 4 years ago

Or you can give me testnet keyPair, I can give a try

kaning commented 4 years ago

Thanks for the response I will try that out.. I have since destroyed that wallet so I can't get you a keypair to test right now... But I will try this out and if I still have issues I will let you know.

Thanks again.

kaning commented 4 years ago

So given this hex, is it possible to tell me why I get Script failed an OP_EQUALVERIFY operation 0100000004227cb7f672c79c66908b7368ae9c4927f4f9432c567cb7933c8b7ef9ba48d3ff000000006a4730440220452883eab215ad0281f2e1a5a79131f9f8873fc9799bbef0791cf5004a389543022033bf0fdf741781811581673ee84666d2f5c95f1a938be237dc7096a2bf446ed80121024403d100458333779be6156c45943493accb7b2f7673f3a92cffcd6cf7efcdcdffffffff227cb7f672c79c66908b7368ae9c4927f4f9432c567cb7933c8b7ef9ba48d3ff010000006b483045022100e0a4aa0d490d9e3e046be4aba717b0b870392f0c7325c278432cbf175cda729502204c1bc11270dca78a78c7136aeeaf6b42b11e7f5316871dc880739cf7b9b3543e0121024403d100458333779be6156c45943493accb7b2f7673f3a92cffcd6cf7efcdcdffffffffadadaacaf2986e3b24f056fee473295f79b150b306fcedd5a74a6b40aaef44cc000000006a47304402201083956c539cf3d5da25493c92af5ee9bcf4ca3624864dbb3b4e2bc06e8c3f2d02201a7e71d52667b39f6eb01b569288dfc2519d3163299d7863c6244f78d944864b0121024403d100458333779be6156c45943493accb7b2f7673f3a92cffcd6cf7efcdcdffffffffdaa7e89b1667eea2e30950d5be79915aba925146e61b0371e568044973f9064f010000006a47304402204ec1001bd1dde42cdbeead07bd970d7b8a2e973155ba982b0a802ea33ddb5f3602202fa3289f07cd1cba374a747fa8489a2f4a3f540da80ed6efcbee3b28f0dda5500121024403d100458333779be6156c45943493accb7b2f7673f3a92cffcd6cf7efcdcdffffffff0288061700000000001976a914a560a38c175c6384797b06227a0fff49ef2c568988acf2770a00000000001976a914f9cb3042e698e21346bbcea183c7cd047774318988ac00000000

longhoangwkm commented 4 years ago

Could you show the code ?!

brunocalmels commented 4 years ago

Hey guys. I'm having trouble also using one legacy P2KPH and one P2WPKH input.

Is it even possible with the library as-is? Cause from exploring the code, I see that when there's one input with witness, it assumes every input has a witness.

longhoangwkm commented 4 years ago

@brunocalmels

Is it even possible with the library as-is?

Possible, Could you show your trouble ?!

brunocalmels commented 4 years ago

This is a test trying to use 1 legacy and 1 segwit UTXO, which fails. Being bob, alice_segwit and alice_legacy three wallets:

      final utxo_legacy = alice_legacy.utxos.last;
      final utxo_segwit = alice_segwit.utxos.last;
      final balance_legacy = utxo_legacy['value'];
      final balance_segwit = utxo_segwit['value'];
      var minerFee = 3000;
      var tx_value_legacy = balance_legacy ~/ 10;
      var tx_value_segwit = balance_segwit ~/ 10;

      final txb = TransactionBuilder(network: specific_network);
      txb.setVersion(1);

      final p2wpkh = P2WPKH(
              data: PaymentData(pubkey: alice_segwit.pubNode.publicKey),
              network: specific_network)
          .data;

      txb.addInput(
        utxo_segwit['hash'],
        utxo_segwit['index'],
        null,
        p2wpkh.output,
      );

      txb.addInput(
        utxo_legacy['hash'],
        utxo_legacy['index'],
      );

      txb.addOutput(bob.publicAddr, tx_value_legacy);

      final change = balance_legacy +
          balance_segwit -
          tx_value_legacy -
          tx_value_segwit -
          minerFee;
      txb.addOutput(alice_legacy.publicAddr, change);

      final alice_signer_legacy = ECPair.fromPrivateKey(
        alice_legacy.privNode.privateKey,
        network: specific_network,
      );

      final alice_signer_segwit = ECPair.fromPrivateKey(
        alice_segwit.privNode.privateKey,
        network: specific_network,
      );

      txb.sign(
        vin: 0,
        keyPair: alice_signer_segwit,
        witnessValue: tx_value_segwit,
      );
      txb.sign(
        vin: 1,
        keyPair: alice_signer_legacy,
      );

      final rawTx = txb.build();

I'm getting:

NoSuchMethodError: The getter 'length' was called on null.
  Receiver: null
  Tried calling: length 

when building the transaction. The trace is:

Transaction.vectorSize package:bitcoin_flutter/src/transaction.dart:259
Transaction._byteLength.<fn> package:bitcoin_flutter/src/transaction.dart:254
ListMixin.fold (dart:collection/list.dart:237:22)
Transaction._byteLength package:bitcoin_flutter/src/transaction.dart:254
Transaction.weight package:bitcoin_flutter/src/transaction.dart:266
Transaction.virtualSize package:bitcoin_flutter/src/transaction.dart:275
TransactionBuilder._build package:bitcoin_flutter/src/transaction_builder.dart:255
TransactionBuilder.build package:bitcoin_flutter/src/transaction_builder.dart:209
main.<fn>.<fn>

Let me add that when using 2 legacy or 2 segwit UTXOs, it doesn't fail (as long as both are the same type).

brunocalmels commented 3 years ago

Hi @longhoangwkm! Any updates on this issue?

gitcoinbot commented 3 years ago

Issue Status: 1. Open 2. Started 3. Submitted 4. Done


This issue now has a funding of 0.003 BTC (102.52 USD @ $34171.73/BTC) attached to it.

brunocalmels commented 3 years ago

I've created a Gitcoin Issue with a bounty in order to get this handy project going forward. Hope it works and helps the project!

gitcoinbot commented 3 years ago

Issue Status: 1. Open 2. Started 3. Submitted 4. Done


Work has been started.

These users each claimed they can complete the work by 265 years, 6 months from now. Please review their action plans below:

1) vmenond has started work.

Just faced a similar issue and found that our solution was in how we were deriving the keys used to sign the outputs.

Could you please share details about the key you are using to sign?

Learn more on the Gitcoin Issue Details page.

brunocalmels commented 3 years ago

I'm just signing using _bitcoinflutter's TransactionBuilder.sign() method, since txa and txb are TransactionBuilder objects.

i5hi commented 3 years ago

@brunocalmels How are you generating your keys? The error is most likely there.

brunocalmels commented 3 years ago

I really don't think so. Both using Legacy or Segwit UTXOs on their own work without any problem.

brunocalmels commented 3 years ago

I've created a test in order to make it reproducible. You can find it here. Just run the test and you should find a stack trace showing the error:

RangeError (byteOffset): Invalid value: Not in inclusive range 0..338: 339
dart:typed_data                                 _ByteDataView.setUint32
Transaction._toBuffer.writeUInt32               lib/src/transaction.dart:328
...

This could interest @i5hi who was after the issue also.

i5hi commented 3 years ago

@brunocalmels our team has been working on a Dart-C FFI for bdk. We will be open sourcing it soon too.

https://github.com/i5hi/rust-mobile has a starting point and tutorial for the Rust side of things. Will update with the Dart side soon.