Fixing bug in transaction serialization for SIGHASH_SINGLE|SIGHASH_ALL of segwit txs

This commit is contained in:
Chris Stewart 2016-12-15 09:02:09 -06:00
parent 9a5864c3bf
commit ab54cdab67
3 changed files with 35 additions and 20 deletions

View file

@ -165,32 +165,33 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
def serializeForSignature(spendingTx: WitnessTransaction, inputIndex: UInt32, script: Seq[ScriptToken], hashType: HashType,
amount: CurrencyUnit): Seq[Byte] = {
val isNotAnyoneCanPay = !HashType.isAnyoneCanPay(hashType)
val isNotSigHashSingle = !(hashType.isInstanceOf[SIGHASH_SINGLE])
val isNotSigHashNone = !(hashType.isInstanceOf[SIGHASH_NONE])
val isNotSigHashSingle = !(HashType.isSIGHASH_SINGLE(hashType.num))
val isNotSigHashNone = !(HashType.isSIGHASH_NONE(hashType.num))
val inputIndexInt = inputIndex.toInt
val outPointHash: Option[Seq[Byte]] = if (isNotAnyoneCanPay) {
val emptyHash = DoubleSha256Digest("0000000000000000000000000000000000000000000000000000000000000000")
val outPointHash: Seq[Byte] = if (isNotAnyoneCanPay) {
val bytes: Seq[Byte] = spendingTx.inputs.flatMap(_.previousOutput.bytes)
Some(CryptoUtil.doubleSHA256(bytes).bytes)
} else None
CryptoUtil.doubleSHA256(bytes).bytes
} else emptyHash.bytes
logger.debug("outPointHash: " + outPointHash.map(BitcoinSUtil.encodeHex(_)))
val sequenceHash: Option[Seq[Byte]] = if (isNotAnyoneCanPay && isNotSigHashNone && isNotSigHashSingle) {
val sequenceHash: Seq[Byte] = if (isNotAnyoneCanPay && isNotSigHashNone && isNotSigHashSingle) {
val bytes = spendingTx.inputs.flatMap(_.sequence.bytes)
Some(CryptoUtil.doubleSHA256(bytes).bytes)
} else None
CryptoUtil.doubleSHA256(bytes).bytes
} else emptyHash.bytes
logger.debug("sequenceHash: " + sequenceHash.map(BitcoinSUtil.encodeHex(_)))
val outputHash: Option[Seq[Byte]] = if (isNotSigHashSingle && isNotSigHashNone) {
val outputHash: Seq[Byte] = if (isNotSigHashSingle && isNotSigHashNone) {
logger.debug("Not SIGHASH_SINGLE & Not SIGHASH_NONE")
//val bytes = spendingTx.outputs.flatMap(_.bytes)
val bytes = spendingTx.outputs.flatMap(o => BitcoinSUtil.decodeHex(RawTransactionOutputParser.write(o)))
Some(CryptoUtil.doubleSHA256(bytes).bytes)
} else if (hashType.isInstanceOf[SIGHASH_SINGLE] && inputIndex < UInt32(spendingTx.outputs.size)) {
CryptoUtil.doubleSHA256(bytes).bytes
} else if (HashType.isSIGHASH_SINGLE(hashType.num) && inputIndex < UInt32(spendingTx.outputs.size)) {
logger.debug("SIGHASH_SINGLE and input index < outputs size")
val output = spendingTx.outputs(inputIndexInt)
val bytes = CryptoUtil.doubleSHA256(output.bytes).bytes
Some(bytes)
} else None
val bytes = CryptoUtil.doubleSHA256(RawTransactionOutputParser.write(output)).bytes
bytes
} else emptyHash.bytes
logger.debug("outputHash: " + outputHash.map(BitcoinSUtil.encodeHex(_)))
logger.debug("Script: " + script)
@ -198,10 +199,10 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
//helper function to flip endianness
val fe: Seq[Byte] => Seq[Byte] = {bytes: Seq[Byte] => BitcoinSUtil.decodeHex(BitcoinSUtil.flipEndianness(bytes)) }
val serializationForSig: Seq[Byte] = fe(spendingTx.version.bytes) ++ outPointHash.getOrElse(Nil) ++ sequenceHash.getOrElse(Nil) ++
val serializationForSig: Seq[Byte] = fe(spendingTx.version.bytes) ++ outPointHash ++ sequenceHash ++
spendingTx.inputs(inputIndexInt).previousOutput.bytes ++ CompactSizeUInt.calculateCompactSizeUInt(scriptBytes).bytes ++
scriptBytes ++ fe(amount.bytes) ++ fe(spendingTx.inputs(inputIndexInt).sequence.bytes) ++
outputHash.getOrElse(Nil) ++ fe(spendingTx.lockTime.bytes) ++ hashType.num.bytes.reverse
outputHash ++ fe(spendingTx.lockTime.bytes) ++ hashType.num.bytes.reverse
logger.info("Serialization for signature for WitnessV0Sig: " + BitcoinSUtil.encodeHex(serializationForSig))
serializationForSig
}

View file

@ -12,10 +12,11 @@ import org.bitcoins.core.policy.Policy
import org.bitcoins.core.serializers.script.ScriptParser
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.ScriptOperationFactory
import org.bitcoins.core.script.{ScriptOperationFactory, ScriptProgram}
import org.bitcoins.core.script.bitwise.OP_EQUALVERIFY
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto._
import org.bitcoins.core.script.flag.{ScriptVerifyP2SH, ScriptVerifyWitness}
import org.bitcoins.core.script.stack.OP_DUP
import org.bitcoins.core.util._
import org.scalatest.{FlatSpec, MustMatchers}
@ -29,8 +30,6 @@ import scala.collection.JavaConversions._
class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers with BitcoinSLogger {
val scriptPubKey = BitcoinjConversions.toScriptPubKey(BitcoinJTestUtil.multiSigScript)
"TransactionSignatureSerializer" must "serialize a transaction for SIGHASH_ALL correctly" in {
val spendingTx = Transaction(BitcoinJTestUtil.multiSigTransaction.bitcoinSerialize())
@ -465,4 +464,18 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers with
BitcoinSUtil.encodeHex(serializedForSig) must be (expected)
}
it must "serialize a p2wsh with SIGHASH_SINGLE|SIGHASH_ANYONECANPAY" in {
val rawTx = "0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000"
val inputIndex = UInt32(1)
val wtx = WitnessTransaction(rawTx)
val scriptWitness = wtx.witness.witnesses(inputIndex.toInt)
val witScriptPubKey = WitnessScriptPubKeyV0("1600144c9c3dfac4207d5d8cb89df5722cb3d712385e3f")
val (_,scriptPubKey) = witScriptPubKey.witnessVersion.rebuild(scriptWitness,witScriptPubKey.witnessProgram).left.get
val amount = Satoshis(Int64(2000))
val serializedForSig = TransactionSignatureSerializer.serializeForSignature(wtx,inputIndex,scriptPubKey.asm,HashType.sigHashSingleAnyoneCanPay, amount)
BitcoinSUtil.encodeHex(serializedForSig) must be ("01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88acd007000000000000ffffffff2d793f9722ac8cbea9b2e0a2929cda4007b8312c6ec3b997088439e48e7aa64e0000000083000000")
}
}

View file

@ -29,4 +29,5 @@ class ScriptProgramTest extends FlatSpec with MustMatchers {
val program3 = ScriptProgram(program, List(ScriptNumber.negativeZero), ScriptProgram.Stack)
program3.stackTopIsTrue must be (false)
}
}