mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-20 10:13:26 +01:00
Successfully parsing raw bitcoin transactions, fixing a variety of bugs throughout the raw parsing api, and adding helpers inside of RawBitcoinSerializer
This commit is contained in:
parent
472e712daf
commit
40d15cd260
@ -24,4 +24,42 @@ trait RawBitcoinSerializer[T] {
|
||||
*/
|
||||
def write(t : T) : String
|
||||
|
||||
|
||||
/**
|
||||
* Adds the amount padding bytes needed to fix the size of the hex string
|
||||
* for instance, vouts are required to be 4 bytes. If the number is just 1
|
||||
* it will only take 1 byte. We need to pad the byte with an extra 3 bytes so the result is
|
||||
* 01000000 instead of just 01
|
||||
* @param bytesNeeded
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
def addPadding(bytesNeeded : Int, hex : String) : String = {
|
||||
val hexCharsNeeded = bytesNeeded * 2
|
||||
val paddingNeeded = hexCharsNeeded - hex.size
|
||||
val padding = for { i <- 0 until paddingNeeded} yield "0"
|
||||
val paddedHex = hex + padding.mkString
|
||||
paddedHex
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a preceding zero to a hex string.
|
||||
* Example: if '1' was passed in, it would return the hex string '01'
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
def addPrecedingZero(hex : String) = {
|
||||
if (hex.size == 1) "0" + hex else hex
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flips the hex chars in a hex strings
|
||||
* Example: abcd would become badc
|
||||
* https://stackoverflow.com/questions/34799611/easiest-way-to-flip-the-endianness-of-a-byte-in-scala/34802270#34802270
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
def flipHalfByte(hex : String) = hex.grouped(2).map(_.reverse).mkString
|
||||
|
||||
}
|
||||
|
@ -50,7 +50,27 @@ trait RawTransactionInputParser extends RawBitcoinSerializer[Seq[TransactionInpu
|
||||
loop(bytes.tail, List(), numInputs).reverse
|
||||
}
|
||||
|
||||
override def write(inputs : Seq[TransactionInput]) = ???
|
||||
override def write(inputs : Seq[TransactionInput]) = {
|
||||
val serializedInputs : Seq[String] = for {
|
||||
input <- inputs
|
||||
} yield {
|
||||
val outPoint = RawTransactionOutPointParser.write(input.previousOutput)
|
||||
val scriptSig = RawScriptSignatureParser.write(input.scriptSignature)
|
||||
val sequenceWithoutPadding = input.sequence.toHexString
|
||||
val paddingNeeded = 8 - sequenceWithoutPadding.size
|
||||
val padding = for { i <- 0 until paddingNeeded} yield "0"
|
||||
val sequence = sequenceWithoutPadding + padding.mkString
|
||||
outPoint + scriptSig + sequence
|
||||
}
|
||||
|
||||
val inputsSizeWithoutPadding = inputs.size.toHexString
|
||||
val inputsSize = if (inputsSizeWithoutPadding.size == 1) "0" + inputsSizeWithoutPadding else inputsSizeWithoutPadding
|
||||
logger.debug("Input size: " + inputsSize)
|
||||
inputsSize + serializedInputs.mkString
|
||||
}
|
||||
}
|
||||
|
||||
object RawTransactionInputParser extends RawTransactionInputParser
|
||||
|
||||
|
||||
|
||||
|
@ -19,9 +19,10 @@ trait RawTransactionOutPointParser extends RawBitcoinSerializer[TransactionOutPo
|
||||
}
|
||||
|
||||
def write(outPoint : TransactionOutPoint) : String = {
|
||||
val indexBytes : List[Byte] = List(0x00,0x00,0x00,outPoint.vout.toByte)
|
||||
val indexHexWithoutPadding : String = addPrecedingZero(outPoint.vout.toHexString)
|
||||
val indexHex = addPadding(4,indexHexWithoutPadding)
|
||||
val littleEndianTxId = ScalacoinUtil.encodeHex(ScalacoinUtil.decodeHex(outPoint.txId).reverse)
|
||||
littleEndianTxId + ScalacoinUtil.encodeHex(indexBytes)
|
||||
littleEndianTxId + indexHex
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.scalacoin.script.crypto
|
||||
|
||||
import org.scalacoin.script.constant.{ScriptConstantImpl, ScriptConstant, ScriptToken}
|
||||
import org.scalacoin.util.ScalacoinUtil
|
||||
import org.scalacoin.util.{CryptoUtil, ScalacoinUtil}
|
||||
|
||||
|
||||
/**
|
||||
@ -14,7 +14,7 @@ trait CryptoInterpreter extends ScalacoinUtil {
|
||||
require(script.headOption.isDefined && script.head == OP_HASH160, "Script operation must be OP_HASH160")
|
||||
val stackTop = stack.head
|
||||
val hash = stackTop match {
|
||||
case ScriptConstantImpl(x) => sha256Hash160(x)
|
||||
case ScriptConstantImpl(x) => CryptoUtil.sha256Hash160(x)
|
||||
case _ => throw new RuntimeException("Stack top should be of type ScriptConstant to call hash160 on it")
|
||||
}
|
||||
(hash :: stack, script.tail)
|
||||
@ -39,16 +39,5 @@ trait CryptoInterpreter extends ScalacoinUtil {
|
||||
|
||||
/*def codeSeparator()*/
|
||||
|
||||
/**
|
||||
* Does the following computation
|
||||
* RIPEMD160(SHA256(hex))
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
private def sha256Hash160(hex : String) : ScriptConstant = {
|
||||
val bytes = decodeHex(hex)
|
||||
val hash = org.bitcoinj.core.Utils.sha256hash160(bytes.toArray)
|
||||
ScriptConstantImpl(encodeHex(hash))
|
||||
|
||||
}
|
||||
}
|
||||
|
45
src/main/scala/org/scalacoin/util/CryptoUtil.scala
Normal file
45
src/main/scala/org/scalacoin/util/CryptoUtil.scala
Normal file
@ -0,0 +1,45 @@
|
||||
package org.scalacoin.util
|
||||
|
||||
import org.bitcoinj.core.Sha256Hash
|
||||
import org.scalacoin.script.constant.{ScriptConstantImpl, ScriptConstant}
|
||||
|
||||
/**
|
||||
* Created by chris on 1/14/16.
|
||||
* Utility cryptographic functions
|
||||
*/
|
||||
trait CryptoUtil extends ScalacoinUtil {
|
||||
|
||||
/**
|
||||
* Does the following computation
|
||||
* RIPEMD160(SHA256(hex))
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
def sha256Hash160(hex : String) : ScriptConstant = {
|
||||
val bytes = decodeHex(hex)
|
||||
val hash = org.bitcoinj.core.Utils.sha256hash160(bytes.toArray)
|
||||
ScriptConstantImpl(encodeHex(hash))
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs sha256(sha256(hex))
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
def doubleSHA256(hex : String) : String = {
|
||||
doubleSHA256(decodeHex(hex))
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs sha256(sha256(hex))
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
def doubleSHA256(bytes : List[Byte]) : String = {
|
||||
val hash : List[Byte] = Sha256Hash.hashTwice(bytes.toArray).toList
|
||||
encodeHex(hash.reverse)
|
||||
}
|
||||
}
|
||||
|
||||
object CryptoUtil extends CryptoUtil
|
@ -2,6 +2,7 @@ package org.scalacoin.marshallers.transaction
|
||||
|
||||
import org.scalacoin.protocol.transaction.TransactionInput
|
||||
import org.scalacoin.script.constant.{ScriptConstantImpl, OP_0}
|
||||
import org.scalacoin.util.TestUtil
|
||||
import org.scalatest.{ FlatSpec, MustMatchers}
|
||||
|
||||
/**
|
||||
@ -14,7 +15,11 @@ class RawTransactionInputParserTest extends FlatSpec with MustMatchers with RawT
|
||||
"85d6b0da2edf96b282030d3f4f79d14cc8c882cfef1b3064170c850660317de100000000" +
|
||||
"6f0047304402207df6dd8dad22d49c3c83d8031733c32a53719278eb7985d3b35b375d776f84f102207054f9209a1e87d55feafc90aa04c33008e5bae9191da22aeaa16efde96f41f00125512102b022902a0fdd71e831c37e4136c2754a59887be0618fb75336d7ab67e2982ff551ae" +
|
||||
"ffffffff"
|
||||
//from txid 44e504f5b7649d215be05ad9f09026dee95201244a3b218013c504a6a49a26ff
|
||||
val rawTxInputs = "02df80e3e6eba7dcd4650281d3c13f140dafbb823a7227a78eb6ee9f6cedd040011b0000006a473044022040f91c48f4011bf2e2edb6621bfa8fb802241de939cb86f1872c99c580ef0fe402204fc27388bc525e1b655b5f5b35f9d601d28602432dd5672f29e0a47f5b8bbb26012102c114f376c98d12a0540c3a81ab99bb1c5234245c05e8239d09f48229f9ebf011ffffffff" +
|
||||
"df80e3e6eba7dcd4650281d3c13f140dafbb823a7227a78eb6ee9f6cedd04001340000006b483045022100cf317c320d078c5b884c44e7488825dab5bcdf3f88c66314ac925770cd8773a7022033fde60d33cc2842ea73fce5d9cf4f8da6fadf414a75b7085efdcd300407f438012102605c23537b27b80157c770cd23e066cd11db3800d3066a38b9b592fc08ae9c70ffffffff"
|
||||
"RawTransactionInputParser" must "parse a raw serialized transaction input" in {
|
||||
println(TestUtil.rawTransaction)
|
||||
val txInputs : Seq[TransactionInput] = read(rawTxInput)
|
||||
txInputs.head.previousOutput.vout must be (0)
|
||||
txInputs.head.previousOutput.txId must be ("e17d316006850c1764301befcf82c8c84cd1794f3f0d0382b296df2edab0d685")
|
||||
@ -28,14 +33,37 @@ class RawTransactionInputParserTest extends FlatSpec with MustMatchers with RawT
|
||||
|
||||
|
||||
it must "parse a multiple raw serialized inputs" in {
|
||||
val rawTxInputs = "02df80e3e6eba7dcd4650281d3c13f140dafbb823a7227a78eb6ee9f6cedd040011b0000006a473044022040f91c48f4011bf2e2edb6621bfa8fb802241de939cb86f1872c99c580ef0fe402204fc27388bc525e1b655b5f5b35f9d601d28602432dd5672f29e0a47f5b8bbb26012102c114f376c98d12a0540c3a81ab99bb1c5234245c05e8239d09f48229f9ebf011ffffffff" +
|
||||
"df80e3e6eba7dcd4650281d3c13f140dafbb823a7227a78eb6ee9f6cedd04001340000006b483045022100cf317c320d078c5b884c44e7488825dab5bcdf3f88c66314ac925770cd8773a7022033fde60d33cc2842ea73fce5d9cf4f8da6fadf414a75b7085efdcd300407f438012102605c23537b27b80157c770cd23e066cd11db3800d3066a38b9b592fc08ae9c70ffffffff"
|
||||
|
||||
val txInputs : Seq[TransactionInput] = RawTransactionInputParser.read(rawTxInputs)
|
||||
|
||||
txInputs.size must be (2)
|
||||
val firstInput = txInputs.head
|
||||
val secondInput = txInputs(1)
|
||||
firstInput.previousOutput.txId must be ("0140d0ed6c9feeb68ea727723a82bbaf0d143fc1d3810265d4dca7ebe6e380df")
|
||||
firstInput.previousOutput.vout must be (27)
|
||||
secondInput.previousOutput.txId must be ("0140d0ed6c9feeb68ea727723a82bbaf0d143fc1d3810265d4dca7ebe6e380df")
|
||||
secondInput.previousOutput.vout must be (52)
|
||||
}
|
||||
|
||||
it must "write a single input" in {
|
||||
val txInputs = RawTransactionInputParser.read(rawTxInput)
|
||||
val serializedInputs = RawTransactionInputParser.write(txInputs)
|
||||
serializedInputs must be (rawTxInput)
|
||||
}
|
||||
|
||||
it must "write multiple inputs" in {
|
||||
val txInputs = RawTransactionInputParser.read(rawTxInputs)
|
||||
val serializedInputs = RawTransactionInputParser.write(txInputs)
|
||||
serializedInputs must be(rawTxInputs)
|
||||
}
|
||||
|
||||
it must "write multiple inputs from a tx with a locktime" in {
|
||||
//from txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||
val rawTxInputs = "02" +
|
||||
"fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff"
|
||||
val txInputs = RawTransactionInputParser.read(rawTxInputs)
|
||||
val serializedTx = RawTransactionInputParser.write(txInputs)
|
||||
serializedTx must be (rawTxInputs)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import org.scalatest.{FlatSpec, MustMatchers}
|
||||
class RawTransactionOutPointParserTest extends FlatSpec with MustMatchers {
|
||||
//txid cad1082e674a7bd3bc9ab1bc7804ba8a57523607c876b8eb2cbe645f2b1803d6
|
||||
val rawOutPoint = "85d6b0da2edf96b282030d3f4f79d14cc8c882cfef1b3064170c850660317de100000000"
|
||||
val rawOutPointLargeVout = "df80e3e6eba7dcd4650281d3c13f140dafbb823a7227a78eb6ee9f6cedd0400134000000"
|
||||
"RawTransactionOutPointMarshaller" must "read a raw outpoint into a native scala TransactionOutPoint" in {
|
||||
val outPoint = RawTransactionOutPointParser.read(rawOutPoint)
|
||||
outPoint.txId must be ("e17d316006850c1764301befcf82c8c84cd1794f3f0d0382b296df2edab0d685")
|
||||
@ -18,8 +19,7 @@ class RawTransactionOutPointParserTest extends FlatSpec with MustMatchers {
|
||||
|
||||
|
||||
it must "parse a large vout for an outpoint" in {
|
||||
val outPointLargeVout = "df80e3e6eba7dcd4650281d3c13f140dafbb823a7227a78eb6ee9f6cedd04001340000006b"
|
||||
val outPoint = RawTransactionOutPointParser.read(outPointLargeVout)
|
||||
val outPoint = RawTransactionOutPointParser.read(rawOutPointLargeVout)
|
||||
outPoint.vout must be (52)
|
||||
outPoint.txId must be ("0140d0ed6c9feeb68ea727723a82bbaf0d143fc1d3810265d4dca7ebe6e380df")
|
||||
}
|
||||
@ -29,5 +29,21 @@ class RawTransactionOutPointParserTest extends FlatSpec with MustMatchers {
|
||||
actualSerialization must be (rawOutPoint)
|
||||
}
|
||||
|
||||
it must "write a outpoint that has a large vout" in {
|
||||
val outPoint = RawTransactionOutPointParser.read(rawOutPointLargeVout)
|
||||
val serializedOutpoint = RawTransactionOutPointParser.write(outPoint)
|
||||
serializedOutpoint must be (rawOutPointLargeVout)
|
||||
}
|
||||
|
||||
it must "write this outpoint with vout index 1" in {
|
||||
//from txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||
val rawOutPoint = "fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e307001000000"
|
||||
|
||||
val outPoint = RawTransactionOutPointParser.read(rawOutPoint)
|
||||
outPoint.txId must be ("70309e35d67db5f2397669eac4f86f9d93704247706f4f3f1bb56f03bdad37fc")
|
||||
val serializedOutPoint = RawTransactionOutPointParser.write(outPoint)
|
||||
serializedOutPoint must be (rawOutPoint)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,23 +10,36 @@ import org.scalatest.{FlatSpec, MustMatchers}
|
||||
class RawTransactionParserTest extends FlatSpec with MustMatchers {
|
||||
|
||||
"RawTransactionParser" must "parse a raw transaction" in {
|
||||
/* val tx : Transaction = RawTransactionParser.read(TestUtil.rawTransaction)
|
||||
|
||||
val tx : Transaction = RawTransactionParser.read(TestUtil.rawTransaction)
|
||||
tx.version must be (1)
|
||||
tx.inputs.size must be (2)
|
||||
tx.outputs.size must be (2)
|
||||
tx.lockTime must be (0)*/
|
||||
//tx.txId must be ("44e504f5b7649d215be05ad9f09026dee95201244a3b218013c504a6a49a26ff")
|
||||
tx.lockTime must be (0)
|
||||
tx.txId must be ("44e504f5b7649d215be05ad9f09026dee95201244a3b218013c504a6a49a26ff")
|
||||
}
|
||||
|
||||
it must "parse a transaction correctly with a locktime" in {
|
||||
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||
val rawTx = "0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
||||
val tx : Transaction = RawTransactionParser.read(rawTx)
|
||||
tx.lockTime must be (1441324800)
|
||||
tx.txId must be ("bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74")
|
||||
tx.lockTime must be (1436996471)
|
||||
}
|
||||
|
||||
/* it must "write a raw transaction" in {
|
||||
val tx : Transaction = RawTransactionParser.read(TestUtil.rawTransaction)
|
||||
val serializedTx = RawTransactionParser.write(tx)
|
||||
|
||||
serializedTx must be (TestUtil.rawTransaction)
|
||||
}*/
|
||||
|
||||
it must "write a transaction with a locktime" in {
|
||||
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||
val rawTxWithLockTime = "0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
||||
val tx = RawTransactionParser.read(rawTxWithLockTime)
|
||||
val serializedTx = RawTransactionParser.write(tx)
|
||||
serializedTx must be (rawTxWithLockTime)
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user