Transaction serialization: hex vs. JSON: what information is lost


(Mikko Ohtamaa) #1

As stated in the documentation, Transaction.toJSON is the only lossless serialization format. I’d like to understand more of this how.

Background: I am trying to create unsigned transactions and send them to an external server for signing. I’d like to use hex string format, as I understand this is more universal than Bitcore’s own JSON format (am I right)? However serializing unsigned transaction using hex string format does not come correctly through the serialization. I’d like to understand this more in-depth, so that I know why this happens. Is this universal issue or just some shortcoming in my way to construct mock transaction for testing?

Below is a sample CoffeeScript code:

# Example how to construct an unsigned transaction for signing

{PublicKey, Transaction, Address} = require 'bitcore'

# http://bitcoin.stackexchange.com/a/36672/5464
# x025e7961b202753f1ebb460984ece0be0ab201d12a63ec3abf79ebfb61f3b70aba
publicKey = new PublicKey(process.argv[2])

# Mock unspent transaction output coming to our address
# http://bitcore.io/guide/unspentoutput.html
utxo = new Transaction.UnspentOutput({
  # Input transcation txid
  "txId" : "a0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9",
  "outputIndex" : 0,
  "address" : publicKey.toAddress(),
  "script" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac",
  "satoshis" : 70000
})

# Create transaction that sends satoshis to some Bitcoin address
# http://bitcore.io/guide/transaction.html
t = new Transaction()
t = t.from([utxo])
t = t.to(Address('1JdmC51Vpo5eaaokicC6bWTwphLYpx7Zpn'), 10000)
t = t.fee(5430)
t = t.change(Address('1JdmC51Vpo5eaaokicC6bWTwphLYpx7Zpn'))

# Do sanity check we constructed a proper transaction
error = t.verify()
if error != true
  console.log("Insane transaction: #{error}")

# Show some log information what goes into transaction for manual inspection
console.log("Input ", t._getInputAmount())
console.log("Output ", t._getOutputAmount())

# Below is some transaction serialization test code

# Check the transaction still works after serialization
# We need to do unsafe serialization as inputs as missing signatures
ser = t.toJSON()

# Alternative: serialize using t.toString()
# ser = t.toString()

console.log("TX:", ser)
t2 = new Transaction(ser)
# This would fail if hex string serialization is used
console.log("Input serialized", t2._getInputAmount())
console.log("Output serialized", t2._getOutputAmount())

# Show the raw unsigned transaction as hexstring
console.log(t.toJSON())


(Braydon Fuller) #2

So here is the basic difference, and an area that I think can use some improvements:

The serialize() and toBuffer() methods will serialize the transaction as expected when broadcasting a transaction over the Bitcoin network, as described: https://bitcoin.org/en/developer-reference#raw-transaction-format This serialization has a reference to the previous outputs, as a fully validating node will already have that information.

On the other hand, since there are cases when creating a transaction that the full UTXO set isn’t available, the toObject() and toJSON() methods are specific for Bitcore’s Transaction, and include otherwise only referenced previous outputs, which are necessary for many of the methods to work correctly, such as _getInputAmount(), _getOutputAmount() and getFee().