
fr.acinq.bitcoin.scalacompat.Transaction.scala Maven / Gradle / Ivy
package fr.acinq.bitcoin.scalacompat
import fr.acinq.bitcoin
import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
import fr.acinq.bitcoin.scalacompat.KotlinUtils._
import fr.acinq.bitcoin.scalacompat.Protocol._
import scodec.bits.ByteVector
import java.io.{InputStream, OutputStream}
import scala.jdk.CollectionConverters.MapHasAsJava
object OutPoint extends BtcSerializer[OutPoint] {
def apply(tx: Transaction, index: Int) = new OutPoint(ByteVector32(tx.hash), index)
override def read(input: InputStream, protocolVersion: Long): OutPoint = kmp2scala(fr.acinq.bitcoin.OutPoint.read(InputStreamWrapper(input), protocolVersion))
override def write(input: OutPoint, out: OutputStream, protocolVersion: Long): Unit = fr.acinq.bitcoin.OutPoint.write(scala2kmp(input), OutputStreamWrapper(out), protocolVersion)
def isCoinbase(input: OutPoint) = scala2kmp(input).isCoinbase
def isNull(input: OutPoint) = isCoinbase(input)
}
/**
* an out point is a reference to a specific output in a specific transaction that we want to claim
*
* @param hash reversed sha256(sha256(tx)) where tx is the transaction we want to refer to
* @param index index of the output in tx that we want to refer to
*/
case class OutPoint(hash: ByteVector32, index: Long) extends BtcSerializable[OutPoint] {
// The genesis block contains inputs with index = -1, so we cannot require it to be >= 0
require(index >= -1)
/**
*
* @return the id of the transaction this output belongs to
*/
val txid: ByteVector32 = hash.reverse
override def serializer: BtcSerializer[OutPoint] = OutPoint
}
object TxIn extends BtcSerializer[TxIn] {
def apply(outPoint: OutPoint, signatureScript: Seq[ScriptElt], sequence: Long): TxIn = new TxIn(outPoint, Script.write(signatureScript), sequence)
override def read(input: InputStream, protocolVersion: Long): TxIn = kmp2scala(fr.acinq.bitcoin.TxIn.read(InputStreamWrapper(input), protocolVersion))
override def write(input: TxIn, out: OutputStream, protocolVersion: Long): Unit = fr.acinq.bitcoin.TxIn.write(scala2kmp(input), OutputStreamWrapper(out),protocolVersion)
override def validate(input: TxIn): Unit = {
require(input.signatureScript.length <= bitcoin.Script.MaxScriptElementSize, s"signature script is ${input.signatureScript.length} bytes, limit is ${bitcoin.Script.MaxScriptElementSize} bytes")
}
def coinbase(script: ByteVector): TxIn = {
require(script.length >= 2 && script.length <= 100, "coinbase script length must be between 2 and 100")
TxIn(OutPoint(ByteVector32.Zeroes, 0xffffffffL), script, sequence = 0xffffffffL)
}
def coinbase(script: Seq[ScriptElt]): TxIn = coinbase(Script.write(script))
}
/**
* Transaction input
*
* @param outPoint Previous output transaction reference
* @param signatureScript Signature script which should match the public key script of the output that we want to spend
* @param sequence Transaction version as defined by the sender. Intended for "replacement" of transactions when
* information is updated before inclusion into a block. Repurposed for OP_CSV (see BIPs 68 & 112)
* @param witness Transaction witness (i.e. what is in sig script for standard transactions).
*/
case class TxIn(outPoint: OutPoint, signatureScript: ByteVector, sequence: Long, witness: ScriptWitness = ScriptWitness.empty) extends BtcSerializable[TxIn] {
def isFinal: Boolean = sequence == bitcoin.TxIn.SEQUENCE_FINAL
def hasWitness: Boolean = witness.isNotNull
override def serializer: BtcSerializer[TxIn] = TxIn
}
object TxOut extends BtcSerializer[TxOut] {
def apply(amount: Satoshi, publicKeyScript: Seq[ScriptElt]): TxOut = new TxOut(amount, Script.write(publicKeyScript))
override def read(input: InputStream, protocolVersion: Long): TxOut = kmp2scala(fr.acinq.bitcoin.TxOut.read(InputStreamWrapper(input), protocolVersion))
override def write(input: TxOut, out: OutputStream, protocolVersion: Long): Unit = fr.acinq.bitcoin.TxOut.write(scala2kmp(input), OutputStreamWrapper(out),protocolVersion)
override def validate(input: TxOut): Unit = {
import input._
require(amount.toLong >= 0, s"invalid txout amount: $amount")
require(amount.toLong <= BtcAmount.MaxMoney, s"invalid txout amount: $amount")
require(publicKeyScript.length < bitcoin.Script.MaxScriptElementSize, s"public key script is ${publicKeyScript.length} bytes, limit is ${bitcoin.Script.MaxScriptElementSize} bytes")
}
}
/**
* Transaction output
*
* @param amount amount in Satoshis
* @param publicKeyScript public key script which sets the conditions for spending this output
*/
case class TxOut(amount: Satoshi, publicKeyScript: ByteVector) extends BtcSerializable[TxOut] {
override def serializer: BtcSerializer[TxOut] = TxOut
}
object ScriptWitness extends BtcSerializer[ScriptWitness] {
val empty = ScriptWitness(Seq.empty[ByteVector])
override def write(t: ScriptWitness, out: OutputStream, protocolVersion: Long): Unit = fr.acinq.bitcoin.ScriptWitness.write(scala2kmp(t), OutputStreamWrapper(out),protocolVersion)
override def read(in: InputStream, protocolVersion: Long): ScriptWitness = kmp2scala(fr.acinq.bitcoin.ScriptWitness.read(InputStreamWrapper(in), protocolVersion))
}
/**
* a script witness is just a stack of data
* there is one script witness per transaction input
*
* @param stack items to be pushed on the stack
*/
case class ScriptWitness(stack: Seq[ByteVector]) extends BtcSerializable[ScriptWitness] {
def isNull = stack.isEmpty
def isNotNull = !isNull
override def serializer: BtcSerializer[ScriptWitness] = ScriptWitness
}
object Transaction extends BtcSerializer[Transaction] {
/**
*
* @param version protocol version (and NOT transaction version !)
* @return true if protocol version specifies that witness data is to be serialized
*/
def serializeTxWitness(version: Long): Boolean = (version & bitcoin.Transaction.SERIALIZE_TRANSACTION_NO_WITNESS) == 0
override def read(input: InputStream, protocolVersion: Long): Transaction = {
val tx = fr.acinq.bitcoin.Transaction.read(InputStreamWrapper(input), protocolVersion)
tx
}
override def write(tx: Transaction, out: OutputStream, protocolVersion: Long): Unit = {
fr.acinq.bitcoin.Transaction.write(tx, OutputStreamWrapper(out), protocolVersion)
}
override def validate(input: Transaction): Unit = {
fr.acinq.bitcoin.Transaction.validate(input)
}
def baseSize(tx: Transaction, protocolVersion: Long = PROTOCOL_VERSION): Int = fr.acinq.bitcoin.Transaction.baseSize(scala2kmp(tx), protocolVersion)
def totalSize(tx: Transaction, protocolVersion: Long = PROTOCOL_VERSION): Int = fr.acinq.bitcoin.Transaction.totalSize(scala2kmp(tx), protocolVersion)
def weight(tx: Transaction, protocolVersion: Long = PROTOCOL_VERSION): Int = totalSize(tx, protocolVersion) + 3 * baseSize(tx, protocolVersion)
def isCoinbase(input: Transaction) = input.txIn.size == 1 && OutPoint.isCoinbase(input.txIn(0).outPoint)
/**
* prepare a transaction for signing a specific input
*
* @param tx input transaction
* @param inputIndex index of the tx input that is being processed
* @param previousOutputScript public key script of the output claimed by this tx input
* @param sighashType signature hash type
* @return a new transaction with proper inputs and outputs according to SIGHASH_TYPE rules
*/
def prepareForSigning(tx: Transaction, inputIndex: Int, previousOutputScript: ByteVector, sighashType: Int): Transaction = {
fr.acinq.bitcoin.Transaction.prepareForSigning(tx, inputIndex, previousOutputScript.toArray, sighashType)
}
/**
* hash a tx for signing (pre-segwit)
*
* @param tx input transaction
* @param inputIndex index of the tx input that is being processed
* @param previousOutputScript public key script of the output claimed by this tx input
* @param sighashType signature hash type
* @return a hash which can be used to sign the referenced tx input
*/
def hashForSigning(tx: Transaction, inputIndex: Int, previousOutputScript: ByteVector, sighashType: Int): ByteVector32 = {
ByteVector32(ByteVector.view(fr.acinq.bitcoin.Transaction.hashForSigning(tx, inputIndex, previousOutputScript.toArray, sighashType)))
}
/**
* hash a tx for signing (pre-segwit)
*
* @param tx input transaction
* @param inputIndex index of the tx input that is being processed
* @param previousOutputScript public key script of the output claimed by this tx input
* @param sighashType signature hash type
* @return a hash which can be used to sign the referenced tx input
*/
def hashForSigning(tx: Transaction, inputIndex: Int, previousOutputScript: Seq[ScriptElt], sighashType: Int): ByteVector32 =
hashForSigning(tx, inputIndex, Script.write(previousOutputScript), sighashType)
/**
* hash a tx for signing
*
* @param tx input transaction
* @param inputIndex index of the tx input that is being processed
* @param previousOutputScript public key script of the output claimed by this tx input
* @param sighashType signature hash type
* @param amount amount of the output claimed by this input
* @return a hash which can be used to sign the referenced tx input
*/
def hashForSigning(tx: Transaction, inputIndex: Int, previousOutputScript: ByteVector, sighashType: Int, amount: Satoshi, signatureVersion: Int): ByteVector32 = {
ByteVector32(ByteVector.view(fr.acinq.bitcoin.Transaction.hashForSigning(tx, inputIndex, previousOutputScript.toArray, sighashType, amount, signatureVersion)))
}
/**
* hash a tx for signing
*
* @param tx input transaction
* @param inputIndex index of the tx input that is being processed
* @param previousOutputScript public key script of the output claimed by this tx input
* @param sighashType signature hash type
* @param amount amount of the output claimed by this input
* @return a hash which can be used to sign the referenced tx input
*/
def hashForSigning(tx: Transaction, inputIndex: Int, previousOutputScript: Seq[ScriptElt], sighashType: Int, amount: Satoshi, signatureVersion: Int): ByteVector32 =
hashForSigning(tx, inputIndex, Script.write(previousOutputScript), sighashType, amount, signatureVersion)
/**
* sign a tx input
*
* @param tx input transaction
* @param inputIndex index of the tx input that is being processed
* @param previousOutputScript public key script of the output claimed by this tx input
* @param sighashType signature hash type, which will be appended to the signature
* @param amount amount of the output claimed by this tx input
* @param signatureVersion signature version (1: segwit, 0: pre-segwit)
* @param privateKey private key
* @return the encoded signature of this tx for this specific tx input
*/
def signInput(tx: Transaction, inputIndex: Int, previousOutputScript: ByteVector, sighashType: Int, amount: Satoshi, signatureVersion: Int, privateKey: PrivateKey): ByteVector = {
ByteVector.view(fr.acinq.bitcoin.Transaction.signInput(tx, inputIndex, scala2kmp(previousOutputScript), sighashType, amount, signatureVersion, privateKey.priv))
}
/**
* sign a tx input
*
* @param tx input transaction
* @param inputIndex index of the tx input that is being processed
* @param previousOutputScript public key script of the output claimed by this tx input
* @param sighashType signature hash type, which will be appended to the signature
* @param amount amount of the output claimed by this tx input
* @param signatureVersion signature version (1: segwit, 0: pre-segwit)
* @param privateKey private key
* @return the encoded signature of this tx for this specific tx input
*/
def signInput(tx: Transaction, inputIndex: Int, previousOutputScript: Seq[ScriptElt], sighashType: Int, amount: Satoshi, signatureVersion: Int, privateKey: PrivateKey): ByteVector =
signInput(tx, inputIndex, Script.write(previousOutputScript), sighashType, amount, signatureVersion, privateKey)
def correctlySpends(tx: Transaction, previousOutputs: Map[OutPoint, TxOut], scriptFlags: Int): Unit = {
fr.acinq.bitcoin.Transaction.correctlySpends(tx, previousOutputs.map { case (o, t) => scala2kmp(o) -> scala2kmp(t) }.asJava, scriptFlags)
}
def correctlySpends(tx: Transaction, inputs: Seq[Transaction], scriptFlags: Int): Unit = {
val prevouts = tx.txIn.map(_.outPoint).map(outpoint => {
val prevTx = inputs.find(_.txid == outpoint.txid).get
val prevOutput = prevTx.txOut(outpoint.index.toInt)
outpoint -> prevOutput
}).toMap
correctlySpends(tx, prevouts, scriptFlags)
}
}
/**
* Transaction
*
* @param version Transaction data format version
* @param txIn Transaction inputs
* @param txOut Transaction outputs
* @param lockTime The block number or timestamp at which this transaction is locked
*/
case class Transaction(version: Long, txIn: Seq[TxIn], txOut: Seq[TxOut], lockTime: Long) extends BtcSerializable[Transaction] {
// standard transaction hash, used to identify transactions (in transactions outputs for example)
lazy val hash: ByteVector32 = Crypto.hash256(Transaction.write(this, bitcoin.Transaction.SERIALIZE_TRANSACTION_NO_WITNESS))
lazy val txid: ByteVector32 = hash.reverse
// witness transaction hash that includes witness data. used to compute the witness commitment included in the coinbase
// transaction of segwit blocks
lazy val whash: ByteVector32 = Crypto.hash256(Transaction.write(this))
lazy val wtxid: ByteVector32 = whash.reverse
lazy val bin: ByteVector = Transaction.write(this)
// this is much easier to use than Scala's default toString
override def toString: String = bin.toHex
/**
*
* @param blockHeight current block height
* @param blockTime current block time
* @return true if the transaction is final
*/
def isFinal(blockHeight: Long, blockTime: Long): Boolean = lockTime match {
case 0 => true
case value if value < bitcoin.Transaction.LOCKTIME_THRESHOLD && value < blockHeight => true
case value if value >= bitcoin.Transaction.LOCKTIME_THRESHOLD && value < blockTime => true
case _ if txIn.exists(!_.isFinal) => false
case _ => true
}
/**
*
* @param i index of the tx input to update
* @param sigScript new signature script
* @return a new transaction that is of copy of this one but where the signature script of the ith input has been replace by sigscript
*/
def updateSigScript(i: Int, sigScript: ByteVector): Transaction = this.copy(txIn = txIn.updated(i, txIn(i).copy(signatureScript = sigScript)))
/**
*
* @param i index of the tx input to update
* @param sigScript new signature script
* @return a new transaction that is of copy of this one but where the signature script of the ith input has been replace by sigscript
*/
def updateSigScript(i: Int, sigScript: Seq[ScriptElt]): Transaction = updateSigScript(i, Script.write(sigScript))
def updateWitness(i: Int, witness: ScriptWitness): Transaction = this.copy(txIn = txIn.updated(i, txIn(i).copy(witness = witness)))
def updateWitnesses(witnesses: Seq[ScriptWitness]): Transaction = {
require(witnesses.length == txIn.length)
witnesses.zipWithIndex.foldLeft(this) {
case (tx, (witness, index)) => tx.updateWitness(index, witness)
}
}
def hasWitness: Boolean = txIn.exists(_.hasWitness)
/**
*
* @param input input to add the tx
* @return a new transaction which includes the newly added input
*/
def addInput(input: TxIn): Transaction = this.copy(txIn = this.txIn :+ input)
/**
*
* @param output output to add to the tx
* @return a new transaction which includes the newly added output
*/
def addOutput(output: TxOut): Transaction = this.copy(txOut = this.txOut :+ output)
def baseSize(protocolVersion: Long = PROTOCOL_VERSION): Int = Transaction.baseSize(this, protocolVersion)
def totalSize(protocolVersion: Long = PROTOCOL_VERSION): Int = Transaction.totalSize(this, protocolVersion)
def weight(protocolVersion: Long = PROTOCOL_VERSION): Int = Transaction.weight(this, protocolVersion)
override def serializer: BtcSerializer[Transaction] = Transaction
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy