bitpay / bitcore

A full stack for bitcoin and blockchain-based applications
https://bitcore.io/
MIT License
4.86k stars 2.09k forks source link

Unsigned transaction can't be serialized and deserialized correctly #1075

Closed dskloet closed 9 years ago

dskloet commented 9 years ago

I have the following code working:

  var tx = new bitcore.Transaction().
      from(outputs).
      to(address, amount).
      change(change).
      fee(fee);
  tx = tx.sign(privateKey);  // this line
  tx = new bitcore.Transaction(tx.serialize());  // this line
  broadcast(tx);

But if I swap the two lines marked as // this line, I get the following error when broadcasting the transaction: Uncaught bitcore.ErrorInvalidState: Invalid state: undefined

dskloet commented 9 years ago

Looking at the JSON for the transaction before and after de/serializing, it seems to have lost the changeScript field, and the input has lost the output field. Passing the unsafe parameter to serialize doesn't seem to make a difference.

dskloet commented 9 years ago

The unsigned transaction can be converted to JSON and back. The signed transaction can be serialized and deserialzed. But: The unsigned transaction can NOT correctly be serialized and deserialzed, and the signed transaction can NOT correctly be converted to JSON and back.

eordano commented 9 years ago

Hi there! Can you try with the latest version, v0.10.3 and see if you still have this issue? Let me know if you run into further issues. If possible, send the UTXOs and private keys (you can modify them for random values).

Remember that the fee needs to be set up in satoshis.

I can correctly run this:

var Transaction = require('bitcore').Transaction;

var t = new Transaction()
  .from({"address":"3BazTqvkvEBcWk7J4sbgRnxUw6rjYrogf9","txid":"dc2e197ab72f71912c39bc23a42d823a3aa8d469fe65eb591c086e60d14c64a0","vout":0,"ts":1418878014,"scriptPubKey":"a9146c8d8b04c6a1e664b1ec20ec932760760c97688e87","amount":0.00300299,"confirmationsFromCache":false}, ["020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2", "0271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b", "03a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd8"], 2)
  .to("38nw4sTs3fCH1YiBjYeQAX1t9eWMxpek8Z", 150000)
  .change("3BazTqvkvEBcWk7J4sbgRnxUw6rjYrogf9")
  .sign("L2U9m5My3cdyN5qX1PH4B7XstGDZFWwyukdX8gj8vsJ3fkrqArQo") // Also tested without this step
  .fee(13400);

console.log(t.toJSON());
dskloet commented 9 years ago

I used http://bitcore.io/playground/bower_components/bitcore/bitcore.min.js Where can I get the latest version? I've tried installing npm multiple times but it never works so building it myself is not an option.

Your code doesn't try to sign after serializing. My full code is below. Again, swapping the lines marked // this line makes it work.

<html>
  <head>
    <script src="bitcore.min.js"></script>
    <script src="bitcore-explorers.min.js"></script>
  <head>
  <body>
    <script>

var bitcore = require('bitcore');
var explorers = require('bitcore-explorers');
var insight = new explorers.Insight('https://test-insight.bitpay.com','testnet');

var fromAddress = 'muPSeU5Zhqt3QGWyxM9RyvcMHKAgacrn91';
var privateKey = 'cQ6XLpaC8PfQeoewDvDjhBLpXdDtWs3C48jjguTrXzJPfsb3iTaG';
var toAddress = 'n2egRNcYDguKApkyK3QT5GtGkeLFZTFq7F';
var amount = 150000;
var fee = 1000;

function main() {
  insight.getUnspentUtxos(fromAddress, handleOutputs);
}

function handleOutputs(err, outputs) {
  var change = fromAddress;

  var tx = new bitcore.Transaction().
      from(outputs).
      to(toAddress, amount).
      change(change).
      fee(fee);
  tx = new bitcore.Transaction(tx.serialize(true));  // this line
  tx = tx.sign(privateKey);  // this line
  broadcast(tx);
}

function broadcast(tx) {
  insight.broadcast(tx, function(err, txid) {
    if (err != null) {
      window.console.log('Broadcast Error:', err);
    } else {
      window.console.log('txid:', txid);
    }
  });
}

window.addEventListener('load', main); 

    </script>
  </body>
</html>
eordano commented 9 years ago

Hi @dskloet,

I'll mark this as a "won't fix at the moment". Please use toJSON, as serialize loses information about the outputs that you are trying to spend. If you take a look at toJSON, you'll see that it contains information about the script from the previous output. We have some ideas to fix this on a further version, I'll keep you posted.

dskloet commented 9 years ago

toJSON() works for the unsigned transaction. For for the signed transaction toJSON() doesn't work and only serialize() works. By this I mean that if I do tx = new bitcore.Transaction(tx.toJSON()); before broadcasting the transaction, the broadcast fails.

eordano commented 9 years ago

Tested on both node and the browser:

var bitcore = require('bitcore');
var explorer = require('bitcore-explorers');
var Transaction = bitcore.Transaction;

var insight = new explorer.Insight('https://test-insight.bitpay.com','testnet');

var fromAddress = 'muPSeU5Zhqt3QGWyxM9RyvcMHKAgacrn91';
var privateKey = 'cQ6XLpaC8PfQeoewDvDjhBLpXdDtWs3C48jjguTrXzJPfsb3iTaG';
var toAddress = 'n2egRNcYDguKApkyK3QT5GtGkeLFZTFq7F';
var amount = 150000;
var fee = 1000;

function handleOutputs(err, outputs) {
  var change = fromAddress;

  var tx = new bitcore.Transaction().
      from(outputs).
      to(toAddress, amount).
      change(change).
      fee(fee);

  tx1 = new bitcore.Transaction(tx.toJSON());
  tx2 = tx1.sign(privateKey);
  tx3 = new bitcore.Transaction(tx2.toJSON());
  tx4 = tx3.sign(privateKey);
  tx5 = new bitcore.Transaction(tx4.toJSON());
  tx6 = tx5.sign(privateKey);
  console.log(tx6.serialize() === tx2.serialize());
  console.log(tx6.toJSON() === tx2.toJSON()); // This may be false because of different serialization orders, but I tried and it turned out to be the same
  insight.broadcast(tx6);
}

insight.getUnspentUtxos(fromAddress, handleOutputs);
eordano commented 9 years ago

You can check your bitcore version with require('bitcore').version! I tested with 0.10.2. Let me know if you have any further doubts

dskloet commented 9 years ago

Does it work if you remove this line tx6 = tx5.sign(privateKey);? Why are you signing the transaction 3 times?

My version is 0.10.2. Is 0.10.3 served somewhere or is the only way to get it to build it myself?

And since I have your attention, if you don't mind. I suddenly started getting SyntaxError: Unexpected token T when doing var tx = new bitcore.Transaction(json); while earlier today this was working fine. Any idea how I can debug this? Which T is unexpected?

The json is '{"version":1,"inputs":[{"prevTxId":"ab0e7f158ff4801f0d95759c0a35f06f1d0fcb943a64654da0ed061560225f18","outputIndex":0,"sequenceNumber":4294967295,"script":"","output":{"satoshis":1000000,"script":"OP_DUP OP_HASH160 20 0x689eea5c9eb3c296af083cb1c7dbdfce0386d113 OP_EQUALVERIFY OP_CHECKSIG"}},{"prevTxId":"faac3caa6131e5ab873b670039c03eed76faf1962a7aed8d40842e133bbd149a","outputIndex":0,"sequenceNumber":4294967295,"script":"","output":{"satoshis":7200000,"script":"OP_DUP OP_HASH160 20 0x689eea5c9eb3c296af083cb1c7dbdfce0386d113 OP_EQUALVERIFY OP_CHECKSIG"}},{"prevTxId":"e7927380272468a9551204c4f7707cc9d43aafa707874cfb0722901e9bd12869","outputIndex":1,"sequenceNumber":4294967295,"script":"","output":{"satoshis":2199000,"script":"OP_DUP OP_HASH160 20 0x689eea5c9eb3c296af083cb1c7dbdfce0386d113 OP_EQUALVERIFY OP_CHECKSIG"}}],"outputs":[{"satoshis":5000000,"script":"OP_DUP OP_HASH160 20 0x2f1b6a429396647d29cfc7254422e0e167408107 OP_EQUALVERIFY OP_CHECKSIG"},{"satoshis":5398000,"script":"OP_DUP OP_HASH160 20 0x689eea5c9eb3c296af083cb1c7dbdfce0386d113 OP_EQUALVERIFY OP_CHECKSIG"}],"nLockTime":0,"changeScript":"OP_DUP OP_HASH160 20 0x689eea5c9eb3c296af083cb1c7dbdfce0386d113 OP_EQUALVERIFY OP_CHECKSIG","changeIndex":1,"fee":1000}'

dskloet commented 9 years ago

Sorry, the error seems to happen while broadcasting. Is something going on with insight at the moment?

dskloet commented 9 years ago

Sorry, to pollute this issue. I'll create a new issue.

dskloet commented 9 years ago

tx4 = tx3.sign(privateKey);

This line shouldn't be necessary because tx3 was already signed before it was converted to JSON. Mind if I look into fixing this at some point?