mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-27 00:06:26 +01:00
* PSBT Serialization/Deserialization (#917) * PSBT Serialization and Deserialization * Remove debug code * Create PSBTParseResult, Add more psbt test cases, and various small nits * PSBT version checking * Remove PSBTHelper, give PSBTRecord and PSBTMap a fromBytes * Fix compile issue, preserve ADT * PSBT Combiner * PSBT Updater * Fix PSBT version number to be read as little endian * Make function names more clear, add scaladoc, require not finalized * Combiner scaladocs * Test case from BIP * Make compatible with older versions * PSBT: Fix for EmptyWitness changes (#968) * PSBTInputMap to UTXOSigningInfo * Remove vars * Add missing param * Fix byteVectorOrdering for when x == y * getUTXOSpendingInfo Tests * Fix formatting errors * Give conditional path default value * formatting fix * Fixed InputPSBTRecord.FinalizedScriptSig using asmBytes (#1004) * Fixed PSBTTest after rebasing onto master * PSBT Constructors and Extraction (#997) * Added PSBT constructors * Revived option to use signers in toUTXOSpendingInfo * Added transaction extraction * Responded to code review * Added extraction test from BIP * Added validation (option) to PSBT extraction * Added property based test for PSBT.extractTransactionAndValidate * Marked PSBT as new code for PropertyCheckConfiguration * PSBT Finalizer (#1002) * Added finalizing functionality to PSBTs * Cleaned things up * Added finalizer test, fails * Check that PSBT is not already finalized when finalizing PSBT * Added P2PKWithTimeout cases * Formatting fix * PSBT Generators (#1019) * Introduced non-finalized constructor * Added property based test comparing finalized and un-finalized construction, it currently fails due to EmptyScriptPubKey not being supported in various ways. Also fixed a ton of bugs! * Fixed easy bugs relating to EmptyScriptPubKey, one is left * Fixed the last bugs (multisig order and 0-of-n stuff) and now tests pass! * Separated out direct finalized PSBT construction from non-finalized PSBT construction * Translated construction work in tests into PSBT generators * Added serialization symmetry test * Made PSBT fee generation dependent on other generators to ensure low enough fees for signing * Responded to code review * Added scaladocs * Fixed arbitraryPSBT to work with older scala versions * Fixed PSBT compilation breakages from rebase * Fixed test breakages from rebase * Validate the PSBT unknown is not a known field * Increase code coverage on PSBT tests * Address review * formatting * Add error messages, rename function * Psbt Signer (#1025) * PSBT Signer * Create addSignature function for PSBTs * Use PartialSignature instead of Tuple * Create extra util functions * scaladoc and simplify case * Formatting fix for psbt scaladocs * Clean up code, add error messages, and scaladoc * add type hints * Change InputPSBTMap.fromUTXOSpendingInfo to use BitcoinSingleSigner * optimize import * Define separator byte for psbt map * getRecords simplification * remove braces, add error message * PSBT stuff assigned to nkohen (#1035) * Responded to psbt review assigned to me * Moved separatorByte to a place where all can use it * PSBT independent record calls (#1037) * PSBT independent record calls * move logic to super class Co-authored-by: Nadav Kohen <nadavk25@gmail.com> * Reworked PSBT finalization to return a Try[PSBT] rather than an Option[PSBT] (#1036) * PSBT signer property based tests (#1038) * PSBT property based tests * Address review * PSBT Combiner property based tests (#1039) * PSBT Combiner property based tests * Create pruneGlobal util function * Switch to arbitrary gen * Use BaseTransaction instead of Transaction * Fix transaction witness to work from base transactions * PSBTs response to Chris's review (#1046) * PSBT chris's review * Rename val, give master fingerprint a val * Formatting fix * Responded to code review from chris assigned to nkohen * Split PSBT.scala into many files * PSBT Docs (#1048) * PSBT docs * Fix compiler error, remove nesting * PSBT output updater tests * Validate psbt outputs * Clean up code * Remove P2WPKHWitnessV0 addition * format * PSBT Updater property based tests * Move code to match block * EmptyScriptWitness check * Address nadav review * Remove unnecessary code, make utxo adder better * Move require and add error message * Move require * PSBT unit tests by Ben * Change to EmptyScriptPubKey * Added unit tests to increase code coverage * responded to review * Move PSBT unit test to separate file * Make unknown generator only generate distinct records * Use groupBy key instead of distinct * use only first element * Combine distinctness by key Co-authored-by: Ben Carman <benthecarman@live.com>
9 KiB
9 KiB
id | title |
---|---|
psbts | 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:
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction.{BaseTransaction, Transaction}
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.crypto.HashType
import scodec.bits._
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
implicit val ec: ExecutionContextExecutor = ExecutionContext.global
// 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")
// To create the initial PSBT all we need to do is
val emptyPSBT = PSBT.fromUnsignedTx(unsignedTransaction)
// 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")
val utxo1 = Transaction(
"0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000")
val psbtWithUTXOs = emptyPSBT
.addUTXOToInput(utxo0, index = 0)
.addUTXOToInput(utxo1, index = 1)
// 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")
val psbtWithUpdatedFirstInput =
psbtWithUTXOs.addRedeemOrWitnessScriptToInput(redeemScript0, index = 0)
// 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")
val witnessScript = ScriptPubKey.fromAsmBytes(
hex"522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae")
// put the data in the PSBT
val psbtWithUpdatedSecondInput = psbtWithUpdatedFirstInput
.addRedeemOrWitnessScriptToInput(redeemScript1, index = 1)
.addRedeemOrWitnessScriptToInput(witnessScript, index = 1)
// 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)
// 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")
val privKey1 = ECPrivateKey.fromWIFToPrivateKey(
"cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d")
val psbtFirstSigF =
psbtWithSigHashFlags
.sign(inputIndex = 0, signer = privKey0)
.flatMap(_.sign(inputIndex = 0, signer = privKey1))
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
}