Partially Signed Bitcoin Transactions
Creating unsigned or partially signed transactions to be passed around to other signers can be a useful for many applications. PSBTs offer a standardized format to serialize the necessary data for signing the transaction, as well as, validating that you in fact want to sign this transaction.
If you want to jump into the details of the specification, you should checkout BIP 174.
Bitcoin-S fully supports PSBTs with functionality for creation, updating, combining, signing, finalizing, and transaction extraction.
An example on a typical PSBT workflow:
implicit val ec: ExecutionContextExecutor = ExecutionContext.global
// ec: ExecutionContextExecutor = scala.concurrent.impl.ExecutionContextImpl$$anon$3@260d8428[Running, parallelism = 2, size = 1, active = 0, running = 0, steals = 8, tasks = 0, submissions = 0]
// First you need an unsigned transaction,
// here we have a standard 2 input, 2 output transaction
// This transaction must be of type BaseTransaction
// and have empty ScriptSignatures for all of it's inputs
val unsignedTransaction = BaseTransaction(
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000")
// unsignedTransaction: BaseTransaction = BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:0),EmptyScriptSignature,UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83:1),EmptyScriptSignature,UInt32Impl(4294967295))),Vector(TransactionOutput(149990000 sats,wpkh(d85c2b71d0060b09c9886aeb815e50991dda124d)), TransactionOutput(100000000 sats,wpkh(00aea9a2e5f0f876a588df5546e8742d1d87008f))),UInt32Impl(0))
// To create the initial PSBT all we need to do is
val emptyPSBT = PSBT.fromUnsignedTx(unsignedTransaction)
// emptyPSBT: PSBT = PSBT(IndexedSeq(UnsignedTransaction(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:0),EmptyScriptSignature,UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83:1),EmptyScriptSignature,UInt32Impl(4294967295))),Vector(TransactionOutput(149990000 sats,wpkh(d85c2b71d0060b09c9886aeb815e50991dda124d)), TransactionOutput(100000000 sats,wpkh(00aea9a2e5f0f876a588df5546e8742d1d87008f))),UInt32Impl(0)))),Vector(IndexedSeq(), IndexedSeq()),Vector(IndexedSeq(), IndexedSeq()))
// Now that we have an empty PSBT we can start updating it with data we know
// First, we want to fill the UTXO fields that we will need for signing and extraction
// The transactions we add are the fully serialized transaction that we are spending from
val utxo0 = Transaction(
"0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000")
// utxo0: Transaction = BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(8b6f65ab71eeab8b2918acc2ea06b79de08b84680b40ae845fd28b013139d7aa:0),P2PKScriptSignature(ECDigitalSignature(3044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01)),UInt32Impl(4294967294))),Vector(TransactionOutput(50000000 sats,sh(0fb9463421696b82c833af241c78c17ddbde4934)), TransactionOutput(4949996240 sats,sh(29ca74f8a08f81999428185c97b5d852e4063f61))),UInt32Impl(101))
val utxo1 = Transaction(
"0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000")
// utxo1: Transaction = WitnessTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:1),P2SHScriptSignature(wpkh(5f275f436b09a8cc9a2eb2a2f528485c68a56323), EmptyScriptSignature),UInt32Impl(4294967294))),Vector(TransactionOutput(4749992920 sats,sh(aed962d6654f9a2b36608eb9d64d2b260db4f111)), TransactionOutput(200000000 sats,sh(b7f5faf40e3d40a5a459b1db3535f2b72fa921e8))),UInt32Impl(101),IndexedSeq(P2WPKHWitnessV0(List(035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79, 3045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad7701))))
val psbtWithUTXOs = emptyPSBT
.addUTXOToInput(utxo0, index = 0)
.addUTXOToInput(utxo1, index = 1)
// psbtWithUTXOs: PSBT = PSBT(IndexedSeq(UnsignedTransaction(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:0),EmptyScriptSignature,UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83:1),EmptyScriptSignature,UInt32Impl(4294967295))),Vector(TransactionOutput(149990000 sats,wpkh(d85c2b71d0060b09c9886aeb815e50991dda124d)), TransactionOutput(100000000 sats,wpkh(00aea9a2e5f0f876a588df5546e8742d1d87008f))),UInt32Impl(0)))),Vector(IndexedSeq(NonWitnessOrUnknownUTXO(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(8b6f65ab71eeab8b2918acc2ea06b79de08b84680b40ae845fd28b013139d7aa:0),P2PKScriptSignature(ECDigitalSignature(3044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01)),UInt32Impl(4294967294))),Vector(TransactionOutput(50000000 sats,sh(0fb9463421696b82c833af241c78c17ddbde4934)), TransactionOutput(4949996240 sats,sh(29ca74f8a08f81999428185c97b5d852e4063f61))),UInt32Impl(101)))), IndexedSeq(NonWitnessOrUnknownUTXO(WitnessTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:1),P2SHScriptSignature(wpkh(5f275f436b09a8cc9a2eb2a2f528485c68a56323), EmptyScriptSignature),UInt32Impl(4294967294))),Vector(TransactionOutput(4749992920 sats,sh(aed962d6654f9a2b36608eb9d64d2b260db4f111)), TransactionOutput(200000000 sats,sh(b7f5faf40e3d40a5a459b1db3535f2b72fa921e8))),UInt32Impl(101),IndexedSeq(P2WPKHWitnessV0(List(035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79, 3045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad7701))))))),Vector(IndexedSeq(), IndexedSeq()))
// After we have the relevant UTXOs we can add the
// redeem scripts, witness scripts, and BIP 32 derivation paths if needed
// In this transaction the first input is a P2SH 2-of-2 multisig
// so we need to add its corresponding redeem script.
// Here we are just using a deserialized version of the redeem script but
// you may generate your ScriptPubKey another way in practice
val redeemScript0 = ScriptPubKey.fromAsmBytes(
hex"5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae")
// redeemScript0: ScriptPubKey = multi(2,029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f,02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7)
val psbtWithUpdatedFirstInput =
psbtWithUTXOs.addRedeemOrWitnessScriptToInput(redeemScript0, index = 0)
// psbtWithUpdatedFirstInput: PSBT = PSBT(IndexedSeq(UnsignedTransaction(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:0),EmptyScriptSignature,UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83:1),EmptyScriptSignature,UInt32Impl(4294967295))),Vector(TransactionOutput(149990000 sats,wpkh(d85c2b71d0060b09c9886aeb815e50991dda124d)), TransactionOutput(100000000 sats,wpkh(00aea9a2e5f0f876a588df5546e8742d1d87008f))),UInt32Impl(0)))),Vector(IndexedSeq(NonWitnessOrUnknownUTXO(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(8b6f65ab71eeab8b2918acc2ea06b79de08b84680b40ae845fd28b013139d7aa:0),P2PKScriptSignature(ECDigitalSignature(3044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01)),UInt32Impl(4294967294))),Vector(TransactionOutput(50000000 sats,sh(0fb9463421696b82c833af241c78c17ddbde4934)), TransactionOutput(4949996240 sats,sh(29ca74f8a08f81999428185c97b5d852e4063f61))),UInt32Impl(101))), RedeemScript(multi(2,029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f,02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7))), IndexedSeq(NonWitnessOrUnknownUTXO(WitnessTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:1),P2SHScriptSignature(wpkh(5f275f436b09a8cc9a2eb2a2f528485c68a56323), EmptyScriptSignature),UInt32Impl(4294967294))),Vector(TransactionOutput(4749992920 sats,sh(aed962d6654f9a2b36608eb9d64d2b260db4f111)), TransactionOutput(200000000 sats,sh(b7f5faf40e3d40a5a459b1db3535f2b72fa921e8))),UInt32Impl(101),IndexedSeq(P2WPKHWitnessV0(List(035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79, 3045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad7701))))))),Vector(IndexedSeq(), IndexedSeq()))
// The second input in this transaction is a P2SH(P2WSH) 2-of-2 multisig
// so we need to add its corresponding redeem script and witness script.
// Here we add them both using the same function, the PSBT updater will
// be able to figure out, based on the available data, where to correctly
val redeemScript1 = ScriptPubKey.fromAsmBytes(
hex"00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903")
// redeemScript1: ScriptPubKey = wsh(8c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903)
val witnessScript = ScriptPubKey.fromAsmBytes(
hex"522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae")
// witnessScript: ScriptPubKey = multi(2,03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc,023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73)
// put the data in the PSBT
val psbtWithUpdatedSecondInput = psbtWithUpdatedFirstInput
.addRedeemOrWitnessScriptToInput(redeemScript1, index = 1)
.addRedeemOrWitnessScriptToInput(witnessScript, index = 1)
// psbtWithUpdatedSecondInput: PSBT = PSBT(IndexedSeq(UnsignedTransaction(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:0),EmptyScriptSignature,UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83:1),EmptyScriptSignature,UInt32Impl(4294967295))),Vector(TransactionOutput(149990000 sats,wpkh(d85c2b71d0060b09c9886aeb815e50991dda124d)), TransactionOutput(100000000 sats,wpkh(00aea9a2e5f0f876a588df5546e8742d1d87008f))),UInt32Impl(0)))),Vector(IndexedSeq(NonWitnessOrUnknownUTXO(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(8b6f65ab71eeab8b2918acc2ea06b79de08b84680b40ae845fd28b013139d7aa:0),P2PKScriptSignature(ECDigitalSignature(3044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01)),UInt32Impl(4294967294))),Vector(TransactionOutput(50000000 sats,sh(0fb9463421696b82c833af241c78c17ddbde4934)), TransactionOutput(4949996240 sats,sh(29ca74f8a08f81999428185c97b5d852e4063f61))),UInt32Impl(101))), RedeemScript(multi(2,029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f,02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7))), IndexedSeq(RedeemScript(wsh(8c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903)), WitnessUTXO(TransactionOutput(200000000 sats,sh(b7f5faf40e3d40a5a459b1db3535f2b72fa921e8))), WitnessScript(multi(2,03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc,023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73)))),Vector(IndexedSeq(), IndexedSeq()))
// Before signing we need to add the needed SigHash flags so we know how to sign the transaction
// If one is not provided it will be assumed to be SigHashAll
val psbtWithSigHashFlags = psbtWithUpdatedSecondInput
.addSigHashTypeToInput(HashType.sigHashAll, index = 0)
.addSigHashTypeToInput(HashType.sigHashAll, index = 1)
// psbtWithSigHashFlags: PSBT = PSBT(IndexedSeq(UnsignedTransaction(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:0),EmptyScriptSignature,UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83:1),EmptyScriptSignature,UInt32Impl(4294967295))),Vector(TransactionOutput(149990000 sats,wpkh(d85c2b71d0060b09c9886aeb815e50991dda124d)), TransactionOutput(100000000 sats,wpkh(00aea9a2e5f0f876a588df5546e8742d1d87008f))),UInt32Impl(0)))),Vector(IndexedSeq(NonWitnessOrUnknownUTXO(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(8b6f65ab71eeab8b2918acc2ea06b79de08b84680b40ae845fd28b013139d7aa:0),P2PKScriptSignature(ECDigitalSignature(3044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01)),UInt32Impl(4294967294))),Vector(TransactionOutput(50000000 sats,sh(0fb9463421696b82c833af241c78c17ddbde4934)), TransactionOutput(4949996240 sats,sh(29ca74f8a08f81999428185c97b5d852e4063f61))),UInt32Impl(101))), RedeemScript(multi(2,029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f,02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7)), SigHashType(SIGHASH_ALL(Int32Impl(1)))), IndexedSeq(RedeemScript(wsh(8c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903)), WitnessUTXO(TransactionOutput(200000000 sats,sh(b7f5faf40e3d40a5a459b1db3535f2b72fa921e8))), WitnessScript(multi(2,03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc,023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73)), SigHashType(SIGHASH_ALL(Int32Impl(1))))),Vector(IndexedSeq(), IndexedSeq()))
// Next, we can now sign the PSBT
// Signing a PSBT will return a Future[PSBT] so this will need to be handled
// correctly in an application
// Here we use the relevant private keys to sign the first input
val privKey0 = ECPrivateKey.fromWIFToPrivateKey(
"cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr")
// privKey0: ECPrivateKey = Masked(ECPrivateKeyImpl)
val privKey1 = ECPrivateKey.fromWIFToPrivateKey(
"cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d")
// privKey1: ECPrivateKey = Masked(ECPrivateKeyImpl)
val psbtFirstSigF =
psbtWithSigHashFlags
.sign(inputIndex = 0, signer = privKey0)
.flatMap(_.sign(inputIndex = 0, signer = privKey1))
// psbtFirstSigF: concurrent.Future[PSBT] = Future(Success(PSBT(IndexedSeq(UnsignedTransaction(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858:0),EmptyScriptSignature,UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83:1),EmptyScriptSignature,UInt32Impl(4294967295))),Vector(TransactionOutput(149990000 sats,wpkh(d85c2b71d0060b09c9886aeb815e50991dda124d)), TransactionOutput(100000000 sats,wpkh(00aea9a2e5f0f876a588df5546e8742d1d87008f))),UInt32Impl(0)))),Vector(IndexedSeq(NonWitnessOrUnknownUTXO(BaseTransactionImpl(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(8b6f65ab71eeab8b2918acc2ea06b79de08b84680b40ae845fd28b013139d7aa:0),P2PKScriptSignature(ECDigitalSignature(3044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01)),UInt32Impl(4294967294))),Vector(TransactionOutput(50000000 sats,sh(0fb9463421696b82c833af241c78c17ddbde4934)), TransactionOutput(4949996240 sats,sh(29ca74f8a08f81999428185c97b5d852e4063f61))),UInt32Impl(101))), RedeemScript(multi(2,029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f,02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7)), SigHashType(SIGHASH_ALL(Int32Impl(1))), PartialSignature(ECPublicKey(029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f),ECDigitalSignature(3044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01)), PartialSignature(ECPublicKey(03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc),ECDigitalSignature(3045022100a806c8b9e660931fce821d0271e5cea1d1c946ed1b915cd848bd5ddf374cb6930220009b19df309183ef0135748f412bc6871f21b2ef76df57fd21a4edbc83e6110901))), IndexedSeq(RedeemScript(wsh(8c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903)), WitnessUTXO(TransactionOutput(200000000 sats,sh(b7f5faf40e3d40a5a459b1db3535f2b72fa921e8))), WitnessScript(multi(2,03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc,023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73)), SigHashType(SIGHASH_ALL(Int32Impl(1))))),Vector(IndexedSeq(), IndexedSeq()))))
psbtFirstSigF.map { psbtFirstSig =>
// In this scenario, let's say that the second input does not belong to us and we need
// another party to sign it. In this case we would need to send the PSBT to the other party.
// The two standard formats for this are in byte form or in base64 you can access these easily.
val bytes = psbtFirstSig.bytes
val base64 = psbtFirstSig.base64
// After the other party has signed their input they can send us back the PSBT with the signatures
// To import we can use any of these functions
val fromBytes = PSBT.fromBytes(
hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
val fromBase64 = PSBT.fromBase64(
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD")
// After we've imported the PSBT we can combine it with our own signed PSBT so we can
// have one PSBT with all of the necessary data
val combinedPSBT = fromBase64.combinePSBT(psbtFirstSig)
// Now that the PSBT has all the necessary data, we can finalize it and extract the transaction
// This will return a Try[PSBT] and will fail if you do not have all the required fields filled
val finalizedPSBT = combinedPSBT.finalizePSBT
// After it has been finalized we can extract the fully signed transaction that is ready
// to be broadcast to the network.
// You can also use extractTransactionAndValidate that will validate if the transaction is valid
val transaction = finalizedPSBT.get.extractTransaction
}
// res0: concurrent.Future[Unit] = Future(Failure(java.lang.IllegalArgumentException: requirement failed: DoubleSha256Digest must always be 32 bytes, got: 0))