mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 18:02:54 +01:00
parent
97187fc4aa
commit
c8a0a0931a
@ -0,0 +1,85 @@
|
||||
package org.bitcoins.commons
|
||||
|
||||
import org.bitcoins.commons.jsonmodels.{SerializedPSBT, SerializedTransaction}
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.psbt.{GlobalPSBTRecord, OutputPSBTRecord, PSBT}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import play.api.libs.json.Json
|
||||
import scodec.bits._
|
||||
|
||||
class SerializedPSBTTest extends BitcoinSUnitTest {
|
||||
|
||||
behavior of "SerializedPSBT"
|
||||
|
||||
it must "correctly decode a psbt" in {
|
||||
val psbt = PSBT.fromBase64(
|
||||
"cHNidP9Fq2AoiZroZUZZ7/Fl0n4dcF8zKWfpD3QzRcAm1QTxUQzzGnHjM5xU+xYUvYSPokH86tLWHVVmhrOQE2d//fPeu6Px6r0LW2DbYkBubiYldhPvO50/3M0wHtHncJ6w/UmdpFVMt/z1iQfbH9U4bq6iLS930BnOiRlc0KX8DQmDnKFdTdiyceBPOmWKSJT+cR1RIQabSiKY6plO4jkSbZ2yFGMBAP3dAgIAAAABG2Z29d/AhiGmmjjrgO9p0LwwYozRbr404YcnQQ+LTQMEAAAAAAAAAAADGQVGPusCAAD9vwFjVyEDkIsYrb9OJzC2oJUtRDntXxhNo7cZeByTHPkuJeCnAmshA4B9R4qr45hWk4fD3F+0um+wJVmP8kFRyXWflgEYKi61IQMLERF8Yx7x85yRmZyD7888+GENPbV2xACyxsAbEJoZpCEDLfkiP2NbzOWOTrM5qIiGFWLDrAYEoYj+6B/p474XWbQhAuNDwvhwWxJHuG/S7CS/bsk/wr8pR9LzCm8eEEqoQ58NIQPlPMn3Y69Fovcn4cexpU9r1whI0yWZ1OwvYHlsngyAyCECyGo6/HPABsWFUwjKuE+mG57duFkwbk17rug85rd0MCohA8ObOSpT1WPHeKKjc3MwDtFM8xr+LRlrDETYazNJhawyIQNqTHMWXX5dNDAuY71BaXfFF8yoHEaQDnDyNudqgfuMQSEC410iVARHWVmvKpcQUFJJx/H41G2/9sJ5jvRnyYIXwQIhA4APXyr35MW/4PPuMRtYg+caUGFqk+12x9wW+JFxHqnwIQMiS1b6HfL93efBgysG0PrlMPwA7eKMSFexdnOGHhOQP1yuZwXdzsmnALF1dqkUCzczWAtdSewwYE3SNlcLhnxJInGIrGjelDmdMwkAABYAFO26WaDgWxcVeSe4lfoysDTitaKJp/r3t2YNAAC4Y1IhA6WEMWgMOWjUk1FZvxYDTRvyVZKDWeCKsBvgJruAngCOIQKeg5DPhM1ghA26JAP1gvhyZ7K/Z2ttavmOd7AyFv2OyyEDjgniAeDz5qiX/5tyDxMJcyMN4yRoYXZwfFgT9x6iKqghAxmojRQWODhnuwBaEtxJHK9AlJXUkPjIOs4o08raE5zfVK5nBF6KYiOxdSEDvbxvoF42LUei1lcblCHsUzeXo7Wr1zKxw5agIRZVWqysaAAAAAAAAQMEggAAAAAAA6jMPk3fqYXY2pAV+YsGMMB2CAwXDBpwMObcmgEz8M0NO07ZYF60Sf8jj0zms7HOPzU3tBtRMROIXzyZreEJOfmqBIZKk6tg78xgjIW8mR+WuQAA")
|
||||
|
||||
val tx = psbt.transaction
|
||||
val decoded = SerializedPSBT.decodePSBT(psbt)
|
||||
|
||||
assert(decoded.global.tx == SerializedTransaction.decodeRawTransaction(tx))
|
||||
assert(decoded.global.version == UInt32.zero)
|
||||
assert(
|
||||
decoded.global.unknowns == Vector(GlobalPSBTRecord.Unknown(
|
||||
hex"ab6028899ae8654659eff165d27e1d705f332967e90f743345c026d504f1510cf31a71e3339c54fb1614bd848fa241fcead2d61d556686b39013677ffdf3debba3f1eabd0b",
|
||||
hex"60db62406e6e26257613ef3b9d3fdccd301ed1e7709eb0fd499da4554cb7fcf58907db1fd5386eaea22d2f77d019ce89195cd0a5fc0d09839ca15d4dd8b271e04f3a658a4894fe711d5121069b4a2298ea994ee239126d9db21463"
|
||||
)))
|
||||
|
||||
assert(decoded.inputs.size == 1)
|
||||
assert(decoded.inputs.head.bip32Paths.isEmpty)
|
||||
assert(decoded.inputs.head.finalizedScriptSig.isEmpty)
|
||||
assert(decoded.inputs.head.finalizedScriptWitness.isEmpty)
|
||||
assert(decoded.inputs.head.nonWitnessUtxo.isEmpty)
|
||||
assert(decoded.inputs.head.witnessUtxo.isEmpty)
|
||||
assert(decoded.inputs.head.redeemScript.isEmpty)
|
||||
assert(decoded.inputs.head.witScript.isEmpty)
|
||||
assert(decoded.inputs.head.proofOfReservesCommitment.isEmpty)
|
||||
assert(decoded.inputs.head.signatures.isEmpty)
|
||||
assert(decoded.inputs.head.unknowns.isEmpty)
|
||||
assert(
|
||||
decoded.inputs.head.sigHashType
|
||||
.contains(HashType.sigHashNoneAnyoneCanPay))
|
||||
|
||||
assert(decoded.outputs.size == 3)
|
||||
assert(decoded.outputs.head.bip32Paths.isEmpty)
|
||||
assert(decoded.outputs.head.redeemScript.isEmpty)
|
||||
assert(decoded.outputs.head.witScript.isEmpty)
|
||||
assert(decoded.outputs.head.unknowns.isEmpty)
|
||||
assert(decoded.outputs(1).bip32Paths.isEmpty)
|
||||
assert(decoded.outputs(1).redeemScript.isEmpty)
|
||||
assert(decoded.outputs(1).witScript.isEmpty)
|
||||
assert(
|
||||
decoded.outputs(1).unknowns == Vector(OutputPSBTRecord.Unknown(
|
||||
hex"a8cc3e",
|
||||
hex"dfa985d8da9015f98b0630c076080c170c1a7030e6dc9a0133f0cd0d3b4ed9605eb449ff238f4ce6b3b1ce3f3537b41b513113885f3c99ade10939f9aa04864a93ab60efcc608c85bc991f96b9"
|
||||
)))
|
||||
assert(decoded.outputs.last.bip32Paths.isEmpty)
|
||||
assert(decoded.outputs.last.redeemScript.isEmpty)
|
||||
assert(decoded.outputs.last.witScript.isEmpty)
|
||||
}
|
||||
|
||||
it must "correctly decode a finalized psbt" in {
|
||||
val psbt = PSBT.fromBase64(
|
||||
"cHNidP8BAP1FAQIAAAABGYuEj8rH1dQ8DYG/wIJoOmA07cMG3qUA2joduT39u3QBAAAAAEhEAAAGZi6ggGgAAAAqBOhp5BGydSEDT4d2r4kKjBDT7N3xtlQRvQOoZMDnvl9kcu+Yz0N+zOWsB9sJYW0AAAAmaiSqIantmOy8chX8u/gjrWjoJwzItqupL01TN2d15WGchnbpGlW08zmpugUAAAcEuQUHI7J1tyGqaVkFAABJZCEDd0QjA5VNPMrHshsJ5ToPa6aXjJClUKFubu0Z3qUgGCmsZyEDCXN1QPwcYCN+5PBjESbL9eI3/Fd2SW/L1kOKOJ6lAsqsaMnuN1dBTAAAFlMU75XVn2xj3vMdoWxBKP6yYswBfsdaZnFQHAAAACZqJKohqe2Q60JHII6xx+YrJFTmibrNi7e8pICEfsbYEMYNKhOtMwAAAAAAAQCKAgAAAAAC7ekoBUtTAABQYyED6l4Vptz8do0rhD+X5Xlmyl5Wwi/fM/EwJmYE/LE4XWhnBfcyAqcAsnUhA/spF0dsJg2ZiBFYnA80MM2Pxou90wok6B8hA44T/vJlaKy4YEecR1gAAB4CSESydXapFHyp4KCUiJddycf+SqnAZI21/aJFiKwAAAAAAQdqRzBEAiBMdpIM1wDaIgn1BJarrd27uzx9kcTixUzQMFGbb0KTsQIgBxNIy+cxhoe1Bnxy/X3+ankfNtC4ydjJcMHlxV0MJqOAIQJUjewAnrbEt88DGQklYoDSLGlNS5Z2C6ukMaVGy+p6EgAAAAAAAAA=")
|
||||
val tx = psbt.transaction
|
||||
val decoded = SerializedPSBT.decodePSBT(psbt)
|
||||
|
||||
assert(decoded.global.tx == SerializedTransaction.decodeRawTransaction(tx))
|
||||
assert(decoded.global.version == UInt32.zero)
|
||||
|
||||
println(Json.prettyPrint(decoded.toJson))
|
||||
assert(decoded.inputs.size == 1)
|
||||
assert(decoded.inputs.head.bip32Paths.isEmpty)
|
||||
assert(decoded.inputs.head.finalizedScriptSig.nonEmpty)
|
||||
assert(decoded.inputs.head.finalizedScriptWitness.isEmpty)
|
||||
assert(decoded.inputs.head.nonWitnessUtxo.nonEmpty)
|
||||
assert(decoded.inputs.head.witnessUtxo.isEmpty)
|
||||
assert(decoded.inputs.head.redeemScript.isEmpty)
|
||||
assert(decoded.inputs.head.witScript.isEmpty)
|
||||
assert(decoded.inputs.head.proofOfReservesCommitment.isEmpty)
|
||||
assert(decoded.inputs.head.signatures.isEmpty)
|
||||
assert(decoded.inputs.head.unknowns.isEmpty)
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package org.bitcoins.commons.jsonmodels
|
||||
|
||||
import org.bitcoins.commons.jsonmodels.SerializedTransaction._
|
||||
import org.bitcoins.commons.serializers.JsonSerializers._
|
||||
import org.bitcoins.core.crypto.ExtPublicKey
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
|
||||
import org.bitcoins.core.psbt._
|
||||
import org.bitcoins.core.script.constant.ScriptToken
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import play.api.libs.json._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
case class SerializedPSBT(
|
||||
global: SerializedPSBTGlobalMap,
|
||||
inputs: Vector[SerializedPSBTInputMap],
|
||||
outputs: Vector[SerializedPSBTOutputMap]) {
|
||||
val toJson: JsValue = Json.toJson(this)
|
||||
}
|
||||
|
||||
case class SerializedPSBTGlobalMap(
|
||||
tx: SerializedTransaction,
|
||||
version: UInt32,
|
||||
xpubs: Option[Vector[ExtPublicKey]],
|
||||
unknowns: Vector[GlobalPSBTRecord.Unknown])
|
||||
|
||||
case class SerializedPSBTInputMap(
|
||||
nonWitnessUtxo: Option[SerializedTransaction],
|
||||
witnessUtxo: Option[SerializedTransactionOutput],
|
||||
signatures: Option[Vector[PartialSignature]],
|
||||
sigHashType: Option[HashType],
|
||||
redeemScript: Option[Vector[ScriptToken]],
|
||||
witScript: Option[Vector[ScriptToken]],
|
||||
bip32Paths: Option[Vector[InputPSBTRecord.BIP32DerivationPath]],
|
||||
finalizedScriptSig: Option[Vector[ScriptToken]],
|
||||
finalizedScriptWitness: Option[SerializedTransactionWitness],
|
||||
proofOfReservesCommitment: Option[ByteVector],
|
||||
unknowns: Vector[InputPSBTRecord.Unknown])
|
||||
|
||||
case class SerializedPSBTOutputMap(
|
||||
redeemScript: Option[Vector[ScriptToken]],
|
||||
witScript: Option[Vector[ScriptToken]],
|
||||
bip32Paths: Option[Vector[OutputPSBTRecord.BIP32DerivationPath]],
|
||||
unknowns: Vector[OutputPSBTRecord.Unknown])
|
||||
|
||||
object SerializedPSBT {
|
||||
|
||||
def decodeGlobalMap(global: GlobalPSBTMap): SerializedPSBTGlobalMap = {
|
||||
val decodedTx = decodeRawTransaction(global.unsignedTransaction.transaction)
|
||||
val version = global.version.version
|
||||
val xpubs = global.extendedPublicKeys.map(_.xpub)
|
||||
val xpubsOpt = if (xpubs.nonEmpty) Some(xpubs) else None
|
||||
val unknownRecords = global.getRecords(PSBTGlobalKeyId.UnknownKeyId)
|
||||
|
||||
SerializedPSBTGlobalMap(decodedTx, version, xpubsOpt, unknownRecords)
|
||||
}
|
||||
|
||||
def decodeInputMap(
|
||||
input: InputPSBTMap,
|
||||
index: Int): SerializedPSBTInputMap = {
|
||||
val prevTxOpt = input.nonWitnessOrUnknownUTXOOpt.map(_.transactionSpent)
|
||||
val nonWitnessUtxo = prevTxOpt.map(decodeRawTransaction)
|
||||
val witnessUtxo = input.witnessUTXOOpt.map(rec =>
|
||||
decodeTransactionOutput(rec.witnessUTXO, index))
|
||||
|
||||
val sigs = input.partialSignatures
|
||||
val sigsOpt = if (sigs.nonEmpty) Some(sigs) else None
|
||||
val hashType = input.sigHashTypeOpt.map(_.hashType)
|
||||
val redeemScript = input.redeemScriptOpt.map(_.redeemScript.asm.toVector)
|
||||
val witScript = input.witnessScriptOpt.map(_.witnessScript.asm.toVector)
|
||||
val bip32Paths = input.BIP32DerivationPaths
|
||||
val bip32PathsOpt = if (bip32Paths.nonEmpty) Some(bip32Paths) else None
|
||||
|
||||
val finalizedScriptSig =
|
||||
input.finalizedScriptSigOpt.map(_.scriptSig.asm.toVector)
|
||||
val finalizedWitScript = input.finalizedScriptWitnessOpt.flatMap(rec =>
|
||||
decodeRawTransactionWitness(rec.scriptWitness))
|
||||
|
||||
val porCommit = input.proofOfReservesCommitmentOpt.map(_.porCommitment)
|
||||
|
||||
val unknowns = input.getRecords(PSBTInputKeyId.UnknownKeyId)
|
||||
|
||||
SerializedPSBTInputMap(nonWitnessUtxo,
|
||||
witnessUtxo,
|
||||
sigsOpt,
|
||||
hashType,
|
||||
redeemScript,
|
||||
witScript,
|
||||
bip32PathsOpt,
|
||||
finalizedScriptSig,
|
||||
finalizedWitScript,
|
||||
porCommit,
|
||||
unknowns)
|
||||
}
|
||||
|
||||
def decodeOutputMap(output: OutputPSBTMap): SerializedPSBTOutputMap = {
|
||||
val redeemScript = output.redeemScriptOpt.map(_.redeemScript.asm.toVector)
|
||||
val witScript = output.witnessScriptOpt.map(_.witnessScript.asm.toVector)
|
||||
val bip32Paths = output.BIP32DerivationPaths
|
||||
val bip32PathsOpt = if (bip32Paths.nonEmpty) Some(bip32Paths) else None
|
||||
val unknowns = output.getRecords(PSBTOutputKeyId.UnknownKeyId)
|
||||
|
||||
SerializedPSBTOutputMap(redeemScript, witScript, bip32PathsOpt, unknowns)
|
||||
}
|
||||
|
||||
def decodePSBT(psbt: PSBT): SerializedPSBT = {
|
||||
val global = decodeGlobalMap(psbt.globalMap)
|
||||
val inputs = psbt.inputMaps.zipWithIndex.map {
|
||||
case (input, index) =>
|
||||
decodeInputMap(input, index)
|
||||
}
|
||||
val outputs = psbt.outputMaps.map(decodeOutputMap)
|
||||
|
||||
SerializedPSBT(global, inputs, outputs)
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package org.bitcoins.commons.jsonmodels
|
||||
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.commons.serializers.JsonSerializers._
|
||||
import org.bitcoins.core.script.constant.{
|
||||
ScriptConstant,
|
||||
ScriptNumberOperation,
|
||||
ScriptToken
|
||||
}
|
||||
import org.bitcoins.crypto.{
|
||||
DoubleSha256DigestBE,
|
||||
ECDigitalSignature,
|
||||
ECPublicKey
|
||||
}
|
||||
import play.api.libs.json._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
case class SerializedTransaction(
|
||||
txid: DoubleSha256DigestBE,
|
||||
wtxid: Option[DoubleSha256DigestBE],
|
||||
version: Int32,
|
||||
size: Long,
|
||||
vsize: Long,
|
||||
weight: Long,
|
||||
locktime: UInt32,
|
||||
vin: Vector[SerializedTransactionInput],
|
||||
vout: Vector[SerializedTransactionOutput]) {
|
||||
val toJson: JsValue = Json.toJson(this)
|
||||
}
|
||||
|
||||
case class SerializedTransactionInput(
|
||||
txid: DoubleSha256DigestBE,
|
||||
hex: String,
|
||||
vout: UInt32,
|
||||
scriptSig: Vector[ScriptToken],
|
||||
txinwitness: Option[SerializedTransactionWitness],
|
||||
sequence: UInt32
|
||||
)
|
||||
|
||||
case class SerializedTransactionWitness(
|
||||
hex: String,
|
||||
scriptType: Option[String],
|
||||
script: Option[Vector[ScriptToken]],
|
||||
pubKey: Option[ECPublicKey],
|
||||
signature: Option[ECDigitalSignature],
|
||||
stack: Option[Vector[ByteVector]])
|
||||
|
||||
case class SerializedTransactionOutput(
|
||||
value: BigDecimal,
|
||||
n: UInt32,
|
||||
scriptPubKey: Vector[ScriptToken],
|
||||
hex: String
|
||||
)
|
||||
|
||||
object SerializedTransaction {
|
||||
|
||||
def tokenToString(token: ScriptToken): String = {
|
||||
token match {
|
||||
case numOp: ScriptNumberOperation => numOp.toString
|
||||
case constOp: ScriptConstant => constOp.bytes.toString
|
||||
case otherOp: ScriptToken => otherOp.toString
|
||||
}
|
||||
}
|
||||
|
||||
def decodeRawTransactionWitness(
|
||||
witness: ScriptWitness): Option[SerializedTransactionWitness] = {
|
||||
witness match {
|
||||
case EmptyScriptWitness => None
|
||||
case p2wpkh: P2WPKHWitnessV0 =>
|
||||
Some(
|
||||
SerializedTransactionWitness(hex = p2wpkh.hex,
|
||||
scriptType = Some("P2WPKH"),
|
||||
script = None,
|
||||
pubKey = Some(p2wpkh.pubKey),
|
||||
signature = Some(p2wpkh.signature),
|
||||
stack = None))
|
||||
case p2wsh: P2WSHWitnessV0 =>
|
||||
Some(
|
||||
SerializedTransactionWitness(hex = p2wsh.hex,
|
||||
scriptType = Some("P2WSH"),
|
||||
script =
|
||||
Some(p2wsh.redeemScript.asm.toVector),
|
||||
pubKey = None,
|
||||
signature = None,
|
||||
stack = Some(p2wsh.stack.toVector.tail)))
|
||||
}
|
||||
}
|
||||
|
||||
def decodeTransactionInput(
|
||||
input: TransactionInput,
|
||||
witnessOpt: Option[ScriptWitness]): SerializedTransactionInput = {
|
||||
val decodedWitnessOpt = witnessOpt.flatMap(decodeRawTransactionWitness)
|
||||
|
||||
SerializedTransactionInput(
|
||||
txid = input.previousOutput.txIdBE,
|
||||
hex = input.hex,
|
||||
vout = input.previousOutput.vout,
|
||||
scriptSig = input.scriptSignature.asm.toVector,
|
||||
txinwitness = decodedWitnessOpt,
|
||||
sequence = input.sequence
|
||||
)
|
||||
}
|
||||
|
||||
def decodeTransactionOutput(
|
||||
output: TransactionOutput,
|
||||
index: Int): SerializedTransactionOutput = {
|
||||
SerializedTransactionOutput(value = output.value.toBigDecimal,
|
||||
n = UInt32(index),
|
||||
scriptPubKey = output.scriptPubKey.asm.toVector,
|
||||
hex = output.hex)
|
||||
}
|
||||
|
||||
def decodeRawTransaction(tx: Transaction): SerializedTransaction = {
|
||||
val inputs = tx.inputs.toVector.zipWithIndex.map {
|
||||
case (input, index) =>
|
||||
val witnessOpt = tx match {
|
||||
case _: NonWitnessTransaction => None
|
||||
case wtx: WitnessTransaction =>
|
||||
Some(wtx.witness.witnesses(index))
|
||||
}
|
||||
|
||||
decodeTransactionInput(input, witnessOpt)
|
||||
}
|
||||
|
||||
val outputs = tx.outputs.toVector.zipWithIndex.map {
|
||||
case (output, index) =>
|
||||
decodeTransactionOutput(output, index)
|
||||
}
|
||||
|
||||
val wtxIdOpt = tx match {
|
||||
case _: NonWitnessTransaction => None
|
||||
case wtx: WitnessTransaction => Some(wtx.wTxIdBE)
|
||||
}
|
||||
|
||||
SerializedTransaction(txid = tx.txIdBE,
|
||||
wtxid = wtxIdOpt,
|
||||
version = tx.version,
|
||||
size = tx.byteSize,
|
||||
vsize = tx.vsize,
|
||||
weight = tx.weight,
|
||||
locktime = tx.lockTime,
|
||||
vin = inputs,
|
||||
vout = outputs)
|
||||
}
|
||||
}
|
@ -17,12 +17,21 @@ import org.bitcoins.commons.serializers.JsonReaders._
|
||||
import org.bitcoins.commons.serializers.JsonWriters._
|
||||
import java.time.LocalDateTime
|
||||
|
||||
import org.bitcoins.commons.jsonmodels.SerializedTransaction.tokenToString
|
||||
import org.bitcoins.commons.jsonmodels._
|
||||
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.AddressType
|
||||
import org.bitcoins.commons.jsonmodels.bitcoind._
|
||||
import org.bitcoins.commons.jsonmodels.wallet._
|
||||
import org.bitcoins.core.psbt.{
|
||||
GlobalPSBTRecord,
|
||||
InputPSBTRecord,
|
||||
OutputPSBTRecord
|
||||
}
|
||||
import org.bitcoins.core.script.constant.ScriptToken
|
||||
import org.bitcoins.crypto._
|
||||
import play.api.libs.functional.syntax._
|
||||
import play.api.libs.json._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.concurrent.duration.DurationLong
|
||||
|
||||
@ -556,6 +565,57 @@ object JsonSerializers {
|
||||
implicit val mempoolSpaceResultReads: Reads[MempoolSpaceResult] =
|
||||
Json.reads[MempoolSpaceResult]
|
||||
|
||||
implicit val byteVectorWrites: Writes[ByteVector] =
|
||||
Writes[ByteVector](bytes => JsString(bytes.toHex))
|
||||
|
||||
implicit val ecDigitalSignatureWrites: Writes[ECDigitalSignature] =
|
||||
Writes[ECDigitalSignature](sig => JsString(sig.hex))
|
||||
|
||||
implicit val ecPublicKeyWrites: Writes[ECPublicKey] =
|
||||
Writes[ECPublicKey](pubKey => JsString(pubKey.hex))
|
||||
|
||||
implicit val scriptTokenWrites: Writes[ScriptToken] =
|
||||
Writes[ScriptToken](token => JsString(tokenToString(token)))
|
||||
|
||||
implicit val doubleSha256DigestBEWrites: Writes[DoubleSha256DigestBE] =
|
||||
Writes[DoubleSha256DigestBE](hash => JsString(hash.hex))
|
||||
|
||||
implicit val int32Writes: Writes[Int32] =
|
||||
Writes[Int32](num => JsNumber(num.toLong))
|
||||
|
||||
implicit val serializedTransactionWitnessWrites: Writes[
|
||||
SerializedTransactionWitness] = Json.writes[SerializedTransactionWitness]
|
||||
|
||||
implicit val serializedTransactionInputWrites: Writes[
|
||||
SerializedTransactionInput] = Json.writes[SerializedTransactionInput]
|
||||
|
||||
implicit val serializedTransactionOutputWrites: Writes[
|
||||
SerializedTransactionOutput] = Json.writes[SerializedTransactionOutput]
|
||||
|
||||
implicit val serializedTransactionWrites: Writes[SerializedTransaction] =
|
||||
Json.writes[SerializedTransaction]
|
||||
|
||||
implicit val unknownPSBTGlobalWrites: Writes[GlobalPSBTRecord.Unknown] =
|
||||
GlobalPSBTRecordUnknownWrites
|
||||
|
||||
implicit val unknownPSBTInputWrites: Writes[InputPSBTRecord.Unknown] =
|
||||
InputPSBTRecordUnknownWrites
|
||||
|
||||
implicit val unknownPSBTOutputWrites: Writes[OutputPSBTRecord.Unknown] =
|
||||
OutputPSBTRecordUnknownWrites
|
||||
|
||||
implicit val serializedPSBTGlobalWrites: Writes[SerializedPSBTGlobalMap] =
|
||||
Json.writes[SerializedPSBTGlobalMap]
|
||||
|
||||
implicit val serializedPSBTInputWrites: Writes[SerializedPSBTInputMap] =
|
||||
Json.writes[SerializedPSBTInputMap]
|
||||
|
||||
implicit val serializedPSBTOutputWrites: Writes[SerializedPSBTOutputMap] =
|
||||
Json.writes[SerializedPSBTOutputMap]
|
||||
|
||||
implicit val serializedPSBTWrites: Writes[SerializedPSBT] =
|
||||
Json.writes[SerializedPSBT]
|
||||
|
||||
// Map stuff
|
||||
implicit def mapDoubleSha256DigestReadsPreV19: Reads[
|
||||
Map[DoubleSha256Digest, GetMemPoolResultPreV19]] =
|
||||
|
@ -10,6 +10,11 @@ import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
||||
import org.bitcoins.core.protocol.script.{ScriptPubKey, WitnessScriptPubKey}
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
|
||||
import org.bitcoins.core.psbt.{
|
||||
GlobalPSBTRecord,
|
||||
InputPSBTRecord,
|
||||
OutputPSBTRecord
|
||||
}
|
||||
import org.bitcoins.core.script.crypto._
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
|
||||
@ -126,4 +131,57 @@ object JsonWriters {
|
||||
JsObject(jsOpts)
|
||||
}
|
||||
}
|
||||
|
||||
implicit object GlobalPSBTRecordUnknownWrites
|
||||
extends Writes[GlobalPSBTRecord.Unknown] {
|
||||
|
||||
override def writes(o: GlobalPSBTRecord.Unknown): JsValue =
|
||||
JsObject(
|
||||
Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex))))
|
||||
}
|
||||
|
||||
implicit object InputPSBTRecordUnknownWrites
|
||||
extends Writes[InputPSBTRecord.Unknown] {
|
||||
|
||||
override def writes(o: InputPSBTRecord.Unknown): JsValue =
|
||||
JsObject(
|
||||
Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex))))
|
||||
}
|
||||
|
||||
implicit object OutputPSBTRecordUnknownWrites
|
||||
extends Writes[OutputPSBTRecord.Unknown] {
|
||||
|
||||
override def writes(o: OutputPSBTRecord.Unknown): JsValue =
|
||||
JsObject(
|
||||
Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex))))
|
||||
}
|
||||
|
||||
implicit object PartialSignatureWrites
|
||||
extends Writes[InputPSBTRecord.PartialSignature] {
|
||||
|
||||
override def writes(o: InputPSBTRecord.PartialSignature): JsValue =
|
||||
JsObject(
|
||||
Seq(("pubkey", JsString(o.pubKey.hex)),
|
||||
("signature", JsString(o.signature.hex))))
|
||||
}
|
||||
|
||||
implicit object InputBIP32PathWrites
|
||||
extends Writes[InputPSBTRecord.BIP32DerivationPath] {
|
||||
|
||||
override def writes(o: InputPSBTRecord.BIP32DerivationPath): JsValue =
|
||||
JsObject(
|
||||
Seq(("pubkey", JsString(o.pubKey.hex)),
|
||||
("master_fingerprint", JsString(o.masterFingerprint.toHex)),
|
||||
("path", JsString(o.path.toString))))
|
||||
}
|
||||
|
||||
implicit object OutputBIP32PathWrites
|
||||
extends Writes[OutputPSBTRecord.BIP32DerivationPath] {
|
||||
|
||||
override def writes(o: OutputPSBTRecord.BIP32DerivationPath): JsValue =
|
||||
JsObject(
|
||||
Seq(("pubkey", JsString(o.pubKey.hex)),
|
||||
("master_fingerprint", JsString(o.masterFingerprint.toHex)),
|
||||
("path", JsString(o.path.toString))))
|
||||
}
|
||||
}
|
||||
|
@ -901,6 +901,19 @@ object ConsoleCli {
|
||||
}))
|
||||
),
|
||||
note(sys.props("line.separator") + "=== PSBT ==="),
|
||||
cmd("decodepsbt")
|
||||
.action((_, conf) => conf.copy(command = DecodePSBT(PSBT.empty)))
|
||||
.text("Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.")
|
||||
.children(
|
||||
arg[PSBT]("psbt")
|
||||
.text("PSBT serialized in hex or base64 format")
|
||||
.required()
|
||||
.action((psbt, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case decode: DecodePSBT =>
|
||||
decode.copy(psbt = psbt)
|
||||
case other => other
|
||||
}))),
|
||||
cmd("combinepsbts")
|
||||
.action((_, conf) => conf.copy(command = CombinePSBTs(Seq.empty)))
|
||||
.text("Combines all the given PSBTs")
|
||||
@ -1255,6 +1268,8 @@ object ConsoleCli {
|
||||
case SendRawTransaction(tx) =>
|
||||
RequestParam("sendrawtransaction", Seq(up.writeJs(tx)))
|
||||
// PSBTs
|
||||
case DecodePSBT(psbt) =>
|
||||
RequestParam("decodepsbt", Seq(up.writeJs(psbt)))
|
||||
case CombinePSBTs(psbts) =>
|
||||
RequestParam("combinepsbts", Seq(up.writeJs(psbts)))
|
||||
case JoinPSBTs(psbts) =>
|
||||
@ -1547,6 +1562,7 @@ object CliCommand {
|
||||
extends CliCommand
|
||||
|
||||
// PSBT
|
||||
case class DecodePSBT(psbt: PSBT) extends CliCommand
|
||||
case class CombinePSBTs(psbts: Seq[PSBT]) extends CliCommand
|
||||
case class JoinPSBTs(psbts: Seq[PSBT]) extends CliCommand
|
||||
case class FinalizePSBT(psbt: PSBT) extends CliCommand
|
||||
|
@ -3,6 +3,7 @@ package org.bitcoins.server
|
||||
import akka.actor.ActorSystem
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.server._
|
||||
import org.bitcoins.commons.jsonmodels.{SerializedPSBT, SerializedTransaction}
|
||||
import org.bitcoins.core.api.core.CoreApi
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
@ -78,8 +79,21 @@ case class CoreRoutes(core: CoreApi)(implicit system: ActorSystem)
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
case Success(DecodeRawTransaction(tx)) =>
|
||||
complete {
|
||||
val jsonStr = SerializedTransaction.decodeRawTransaction(tx)
|
||||
Server.httpSuccess(jsonStr)
|
||||
val decoded = SerializedTransaction.decodeRawTransaction(tx)
|
||||
val uJson = ujson.read(decoded.toJson.toString())
|
||||
Server.httpSuccess(uJson)
|
||||
}
|
||||
}
|
||||
|
||||
case ServerCommand("decodepsbt", arr) =>
|
||||
DecodePSBT.fromJsArr(arr) match {
|
||||
case Failure(exception) =>
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
case Success(DecodePSBT(psbt)) =>
|
||||
complete {
|
||||
val decoded = SerializedPSBT.decodePSBT(psbt)
|
||||
val uJson = ujson.read(decoded.toJson.toString())
|
||||
Server.httpSuccess(uJson)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -286,6 +286,25 @@ object DecodeRawTransaction extends ServerJsonModels {
|
||||
}
|
||||
}
|
||||
|
||||
case class DecodePSBT(psbt: PSBT)
|
||||
|
||||
object DecodePSBT extends ServerJsonModels {
|
||||
|
||||
def fromJsArr(jsArr: ujson.Arr): Try[DecodePSBT] = {
|
||||
jsArr.arr.toList match {
|
||||
case psbtJs :: Nil =>
|
||||
Try {
|
||||
val psbt = jsToPSBT(psbtJs)
|
||||
DecodePSBT(psbt)
|
||||
}
|
||||
case other =>
|
||||
Failure(
|
||||
new IllegalArgumentException(
|
||||
s"Bad number of arguments: ${other.length}. Expected: 1"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class Rescan(
|
||||
batchSize: Option[Int],
|
||||
startBlock: Option[BlockStamp],
|
||||
|
@ -101,7 +101,7 @@ case class GlobalPSBTMap(elements: Vector[GlobalPSBTRecord])
|
||||
getRecords(VersionKeyId).headOption.getOrElse(Version(UInt32.zero))
|
||||
}
|
||||
|
||||
private def getRecords(key: PSBTGlobalKeyId): Vector[key.RecordType] = {
|
||||
def getRecords(key: PSBTGlobalKeyId): Vector[key.RecordType] = {
|
||||
super.getRecords(key, PSBTGlobalKeyId)
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord])
|
||||
getRecords(ProofOfReservesCommitmentKeyId).headOption
|
||||
}
|
||||
|
||||
private def getRecords(key: PSBTInputKeyId): Vector[key.RecordType] = {
|
||||
def getRecords(key: PSBTInputKeyId): Vector[key.RecordType] = {
|
||||
super.getRecords(key, PSBTInputKeyId)
|
||||
}
|
||||
|
||||
@ -832,7 +832,7 @@ case class OutputPSBTMap(elements: Vector[OutputPSBTRecord])
|
||||
getRecords(BIP32DerivationPathKeyId)
|
||||
}
|
||||
|
||||
private def getRecords(key: PSBTOutputKeyId): Vector[key.RecordType] = {
|
||||
def getRecords(key: PSBTOutputKeyId): Vector[key.RecordType] = {
|
||||
super.getRecords(key, PSBTOutputKeyId)
|
||||
}
|
||||
|
||||
|
@ -194,6 +194,8 @@ For more information on how to use our built in `cli` to interact with the serve
|
||||
- `tx` - Transaction serialized in hex
|
||||
|
||||
#### PSBT
|
||||
- `decodepsbt` `psbt` - Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.
|
||||
- `psbt` - PSBT serialized in hex or base64 format
|
||||
- `combinepsbts` `psbts` - Combines all the given PSBTs
|
||||
- `psbts` - PSBTs serialized in hex or base64 format
|
||||
- `joinpsbts` `psbts` - Combines all the given PSBTs
|
||||
|
Loading…
Reference in New Issue
Block a user