Decode PSBT function (#2237)

* Decode PSBT function

* Add test
This commit is contained in:
Ben Carman 2020-11-06 06:56:46 -06:00 committed by GitHub
parent 97187fc4aa
commit c8a0a0931a
10 changed files with 522 additions and 5 deletions

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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]] =

View File

@ -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))))
}
}

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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],

View File

@ -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)
}

View File

@ -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