All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.fr.acinq.bitcoin.psbt.Psbt.kt Maven / Gradle / Ivy

Go to download

A simple Kotlin Multiplatform library which implements most of the bitcoin protocol

The newest version!
/*
 * Copyright 2021 ACINQ SAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.acinq.bitcoin.psbt

import fr.acinq.bitcoin.*
import fr.acinq.bitcoin.crypto.Pack
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.readNBytes
import fr.acinq.bitcoin.utils.Either
import fr.acinq.bitcoin.utils.getOrElse
import kotlin.jvm.JvmField
import kotlin.jvm.JvmStatic

/**
 * A partially signed bitcoin transaction: see https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki.
 *
 * @param global global psbt data containing the transaction to be signed.
 * @param inputs signing data for each input of the transaction to be signed (order matches the unsigned tx).
 * @param outputs signing data for each output of the transaction to be signed (order matches the unsigned tx).
 */
public data class Psbt(@JvmField val global: Global, @JvmField val inputs: List, @JvmField val outputs: List) {

    init {
        require(global.tx.txIn.size == inputs.size) { "there must be one partially signed input per input of the unsigned tx" }
        require(global.tx.txOut.size == outputs.size) { "there must be one partially signed output per output of the unsigned tx" }
    }

    /**
     * Implements the PSBT creator role; initializes a PSBT for the given unsigned transaction.
     *
     * @param tx unsigned transaction skeleton.
     * @return the psbt with empty inputs and outputs.
     */
    public constructor(tx: Transaction) : this(
        Global(Version, tx.copy(txIn = tx.txIn.map { it.copy(signatureScript = ByteVector.empty, witness = ScriptWitness.empty) }), listOf(), listOf()),
        tx.txIn.map { Input.PartiallySignedInputWithoutUtxo(null, mapOf(), setOf(), setOf(), setOf(), setOf(), null, mapOf(), null, listOf()) },
        tx.txOut.map { Output.UnspecifiedOutput(mapOf(), null, mapOf(), listOf()) }
    )

    /**
     * Implements the PSBT updater role; adds information about a given segwit utxo.
     * When you have access to the complete input transaction, you should prefer [[updateWitnessInputTx]].
     *
     * @param outPoint utxo being spent.
     * @param txOut transaction output for the provided outPoint.
     * @param redeemScript redeem script if known and applicable (when using p2sh-embedded segwit).
     * @param witnessScript witness script if known and applicable (when using p2wsh).
     * @param sighashType sighash type if one should be specified.
     * @param derivationPaths derivation paths for keys used by this utxo.
     * @return psbt with the matching input updated.
     */
    public fun updateWitnessInput(
        outPoint: OutPoint,
        txOut: TxOut,
        redeemScript: List? = null,
        witnessScript: List? = null,
        sighashType: Int? = null,
        derivationPaths: Map = mapOf(),
        taprootKeySignature: ByteVector? = null,
        taprootInternalKey: XonlyPublicKey? = null,
        taprootDerivationPaths: Map = mapOf()
    ): Either {
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outPoint }
        if (inputIndex < 0) return Either.Left(UpdateFailure.InvalidInput("psbt transaction does not spend the provided outpoint"))
        val updatedInput = when (val input = inputs[inputIndex]) {
            is Input.PartiallySignedInputWithoutUtxo -> Input.WitnessInput.PartiallySignedWitnessInput(
                txOut,
                input.nonWitnessUtxo,
                sighashType ?: input.sighashType,
                input.partialSigs,
                input.derivationPaths + derivationPaths,
                redeemScript,
                witnessScript,
                input.ripemd160,
                input.sha256,
                input.hash160,
                input.hash256,
                taprootKeySignature ?: input.taprootKeySignature,
                input.taprootDerivationPaths + taprootDerivationPaths,
                taprootInternalKey ?: input.taprootInternalKey,
                input.unknown
            )
            is Input.WitnessInput.PartiallySignedWitnessInput -> input.copy(
                txOut = txOut,
                redeemScript = redeemScript ?: input.redeemScript,
                witnessScript = witnessScript ?: input.witnessScript,
                sighashType = sighashType ?: input.sighashType,
                derivationPaths = input.derivationPaths + derivationPaths,
                taprootInternalKey = taprootInternalKey ?: input.taprootInternalKey,
                taprootDerivationPaths = input.taprootDerivationPaths + taprootDerivationPaths
            )
            is Input.NonWitnessInput.PartiallySignedNonWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been updated with non-segwit data"))
            is Input.FinalizedInputWithoutUtxo -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been finalized"))
            is Input.WitnessInput.FinalizedWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been finalized"))
            is Input.NonWitnessInput.FinalizedNonWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been finalized"))
        }
        return Either.Right(this.copy(inputs = inputs.updated(inputIndex, updatedInput)))
    }

    /**
     * Implements the PSBT updater role; adds information about a given segwit utxo.
     * Note that we always fill the nonWitnessUtxo (see https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#cite_note-7).
     *
     * @param inputTx transaction containing the utxo.
     * @param outputIndex index of the utxo in the inputTx.
     * @param redeemScript redeem script if known and applicable (when using p2sh-embedded segwit).
     * @param witnessScript witness script if known and applicable (when using p2wsh).
     * @param sighashType sighash type if one should be specified.
     * @param derivationPaths derivation paths for keys used by this utxo.
     * @param taprootInternalKey internal key used by this utxo.
     * @param taprootDerivationPaths taproot derivation paths for keys used by this utxo.
     * @return psbt with the matching input updated.
     */
    public fun updateWitnessInputTx(
        inputTx: Transaction,
        outputIndex: Int,
        redeemScript: List? = null,
        witnessScript: List? = null,
        sighashType: Int? = null,
        derivationPaths: Map = mapOf(),
        taprootKeySignature: ByteVector? = null,
        taprootInternalKey: XonlyPublicKey? = null,
        taprootDerivationPaths: Map = mapOf()
    ): Either {
        if (outputIndex >= inputTx.txOut.size) return Either.Left(UpdateFailure.InvalidInput("output index must exist in the input tx"))
        val outpoint = OutPoint(inputTx, outputIndex.toLong())
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outpoint }
        if (inputIndex < 0) return Either.Left(UpdateFailure.InvalidInput("psbt transaction does not spend the provided outpoint"))
        val updatedInput = when (val input = inputs[inputIndex]) {
            is Input.PartiallySignedInputWithoutUtxo -> Input.WitnessInput.PartiallySignedWitnessInput(
                inputTx.txOut[outputIndex],
                inputTx,
                sighashType ?: input.sighashType,
                input.partialSigs,
                input.derivationPaths + derivationPaths,
                redeemScript,
                witnessScript,
                input.ripemd160,
                input.sha256,
                input.hash160,
                input.hash256,
                taprootKeySignature ?: input.taprootKeySignature,
                input.taprootDerivationPaths + taprootDerivationPaths,
                taprootInternalKey ?: input.taprootInternalKey,
                input.unknown
            )
            is Input.WitnessInput.PartiallySignedWitnessInput -> input.copy(
                txOut = inputTx.txOut[outputIndex],
                nonWitnessUtxo = inputTx,
                redeemScript = redeemScript ?: input.redeemScript,
                witnessScript = witnessScript ?: input.witnessScript,
                sighashType = sighashType ?: input.sighashType,
                derivationPaths = input.derivationPaths + derivationPaths,
                taprootInternalKey = taprootInternalKey ?: input.taprootInternalKey,
                taprootDerivationPaths = input.taprootDerivationPaths + taprootDerivationPaths
            )
            is Input.NonWitnessInput.PartiallySignedNonWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been updated with non-segwit data"))
            is Input.FinalizedInputWithoutUtxo -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been finalized"))
            is Input.WitnessInput.FinalizedWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been finalized"))
            is Input.NonWitnessInput.FinalizedNonWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update segwit input: it has already been finalized"))
        }
        return Either.Right(this.copy(inputs = inputs.updated(inputIndex, updatedInput)))
    }

    /**
     * Implements the PSBT updater role; adds information about a given non-segwit utxo.
     *
     * @param inputTx transaction containing the utxo.
     * @param outputIndex index of the utxo in the inputTx.
     * @param redeemScript redeem script if known and applicable (when using p2sh).
     * @param sighashType sighash type if one should be specified.
     * @param derivationPaths derivation paths for keys used by this utxo.
     * @return psbt with the matching input updated.
     */
    public fun updateNonWitnessInput(
        inputTx: Transaction,
        outputIndex: Int,
        redeemScript: List? = null,
        sighashType: Int? = null,
        derivationPaths: Map = mapOf()
    ): Either {
        if (outputIndex >= inputTx.txOut.size) return Either.Left(UpdateFailure.InvalidInput("output index must exist in the input tx"))
        val outpoint = OutPoint(inputTx, outputIndex.toLong())
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outpoint }
        if (inputIndex < 0) return Either.Left(UpdateFailure.InvalidInput("psbt transaction does not spend the provided outpoint"))
        val updatedInput = when (val input = inputs[inputIndex]) {
            is Input.PartiallySignedInputWithoutUtxo -> Input.NonWitnessInput.PartiallySignedNonWitnessInput(
                inputTx,
                outputIndex,
                sighashType ?: input.sighashType,
                input.partialSigs,
                input.derivationPaths + derivationPaths,
                redeemScript,
                input.ripemd160,
                input.sha256,
                input.hash160,
                input.hash256,
                input.unknown
            )
            is Input.NonWitnessInput.PartiallySignedNonWitnessInput -> input.copy(
                inputTx = inputTx,
                outputIndex = outputIndex,
                redeemScript = redeemScript ?: input.redeemScript,
                sighashType = sighashType ?: input.sighashType,
                derivationPaths = input.derivationPaths + derivationPaths
            )
            is Input.WitnessInput.PartiallySignedWitnessInput -> input.copy(
                nonWitnessUtxo = inputTx,
                redeemScript = redeemScript ?: input.redeemScript,
                sighashType = sighashType ?: input.sighashType,
                derivationPaths = input.derivationPaths + derivationPaths
            )
            is Input.FinalizedInputWithoutUtxo -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update non-segwit input: it has already been finalized"))
            is Input.WitnessInput.FinalizedWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update non-segwit input: it has already been finalized"))
            is Input.NonWitnessInput.FinalizedNonWitnessInput -> return Either.Left(UpdateFailure.CannotUpdateInput(inputIndex, "cannot update non-segwit input: it has already been finalized"))
        }
        return Either.Right(this.copy(inputs = inputs.updated(inputIndex, updatedInput)))
    }

    public fun updatePreimageChallenges(outPoint: OutPoint, ripemd160: Set, sha256: Set, hash160: Set, hash256: Set): Either {
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outPoint }
        if (inputIndex < 0) return Either.Left(UpdateFailure.InvalidInput("psbt transaction does not spend the provided outpoint"))
        return updatePreimageChallenges(inputIndex, ripemd160, sha256, hash160, hash256)
    }

    public fun updatePreimageChallenges(inputIndex: Int, ripemd160: Set, sha256: Set, hash160: Set, hash256: Set): Either {
        if (inputIndex >= inputs.size) return Either.Left(UpdateFailure.InvalidInput("input index must exist in the input tx"))
        val updatedInput = when (val input = inputs[inputIndex]) {
            is Input.PartiallySignedInputWithoutUtxo -> input.copy(ripemd160 = ripemd160 + input.ripemd160, sha256 = sha256 + input.sha256, hash160 = hash160 + input.hash160, hash256 = hash256 + input.hash256)
            is Input.WitnessInput.PartiallySignedWitnessInput -> input.copy(ripemd160 = ripemd160 + input.ripemd160, sha256 = sha256 + input.sha256, hash160 = hash160 + input.hash160, hash256 = hash256 + input.hash256)
            is Input.NonWitnessInput.PartiallySignedNonWitnessInput -> input.copy(ripemd160 = ripemd160 + input.ripemd160, sha256 = sha256 + input.sha256, hash160 = hash160 + input.hash160, hash256 = hash256 + input.hash256)
            is Input.WitnessInput.FinalizedWitnessInput -> input.copy(ripemd160 = ripemd160 + input.ripemd160, sha256 = sha256 + input.sha256, hash160 = hash160 + input.hash160, hash256 = hash256 + input.hash256)
            is Input.NonWitnessInput.FinalizedNonWitnessInput -> input.copy(ripemd160 = ripemd160 + input.ripemd160, sha256 = sha256 + input.sha256, hash160 = hash160 + input.hash160, hash256 = hash256 + input.hash256)
            is Input.FinalizedInputWithoutUtxo -> input.copy(ripemd160 = ripemd160 + input.ripemd160, sha256 = sha256 + input.sha256, hash160 = hash160 + input.hash160, hash256 = hash256 + input.hash256)
        }
        return Either.Right(this.copy(inputs = inputs.updated(inputIndex, updatedInput)))
    }

    /**
     * Add details for a segwit output.
     *
     * @param outputIndex index of the output in the psbt.
     * @param witnessScript witness script if known and applicable (when using p2wsh).
     * @param redeemScript redeem script if known and applicable (when using p2sh-embedded segwit).
     * @param derivationPaths derivation paths for keys used by this output.
     * @return psbt with the matching output updated.
     */
    public fun updateWitnessOutput(
        outputIndex: Int,
        witnessScript: List? = null,
        redeemScript: List? = null,
        derivationPaths: Map = mapOf(),
        taprootInternalKey: XonlyPublicKey? = null,
        taprootDerivationPaths: Map = mapOf()
    ): Either {
        if (outputIndex >= global.tx.txOut.size) return Either.Left(UpdateFailure.InvalidInput("output index must exist in the global tx"))
        val updatedOutput = when (val output = outputs[outputIndex]) {
            is Output.NonWitnessOutput -> return Either.Left(UpdateFailure.CannotUpdateOutput(outputIndex, "cannot update segwit output: it has already been updated with non-segwit data"))
            is Output.WitnessOutput -> output.copy(
                witnessScript = witnessScript ?: output.witnessScript,
                redeemScript = redeemScript ?: output.redeemScript,
                derivationPaths = output.derivationPaths + derivationPaths,
                taprootInternalKey = taprootInternalKey ?: output.taprootInternalKey,
                taprootDerivationPaths = output.taprootDerivationPaths + taprootDerivationPaths
            )
            is Output.UnspecifiedOutput -> Output.WitnessOutput(
                witnessScript,
                redeemScript,
                output.derivationPaths + derivationPaths,
                taprootInternalKey ?: output.taprootInternalKey,
                output.taprootDerivationPaths + taprootDerivationPaths,
                output.unknown
            )
        }
        return Either.Right(this.copy(outputs = outputs.updated(outputIndex, updatedOutput)))
    }

    /**
     * Add details for a non-segwit output.
     *
     * @param outputIndex index of the output in the psbt.
     * @param redeemScript redeem script if known and applicable (when using p2sh).
     * @param derivationPaths derivation paths for keys used by this output.
     * @return psbt with the matching output updated.
     */
    public fun updateNonWitnessOutput(
        outputIndex: Int,
        redeemScript: List? = null,
        derivationPaths: Map = mapOf()
    ): Either {
        if (outputIndex >= global.tx.txOut.size) return Either.Left(UpdateFailure.InvalidInput("output index must exist in the global tx"))
        val updatedOutput = when (val output = outputs[outputIndex]) {
            is Output.NonWitnessOutput -> output.copy(
                redeemScript = redeemScript ?: output.redeemScript,
                derivationPaths = output.derivationPaths + derivationPaths
            )
            is Output.WitnessOutput -> return Either.Left(UpdateFailure.CannotUpdateOutput(outputIndex, "cannot update non-segwit output: it has already been updated with segwit data"))
            is Output.UnspecifiedOutput -> Output.NonWitnessOutput(redeemScript, output.derivationPaths + derivationPaths, output.unknown)
        }
        return Either.Right(this.copy(outputs = outputs.updated(outputIndex, updatedOutput)))
    }

    /**
     * Implements the PSBT signer role: sign a given input.
     * The caller needs to carefully verify that it wants to spend that input, and that the unsigned transaction matches
     * what it expects.
     *
     * @param priv private key used to sign the input.
     * @param outPoint input that should be signed.
     * @return the psbt with a partial signature added (other inputs will not be modified).
     */
    public fun sign(priv: PrivateKey, outPoint: OutPoint): Either {
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outPoint }
        if (inputIndex < 0) return Either.Left(UpdateFailure.InvalidInput("psbt transaction does not spend the provided outpoint"))
        return sign(priv, inputIndex)
    }

    /**
     * Implements the PSBT signer role: sign a given input.
     * The caller needs to carefully verify that it wants to spend that input, and that the unsigned transaction matches
     * what it expects.
     *
     * @param priv private key used to sign the input.
     * @param inputIndex index of the input that should be signed.
     * @return the psbt with a partial signature added (other inputs will not be modified).
     */
    public fun sign(priv: PrivateKey, inputIndex: Int): Either {
        if (inputIndex >= inputs.size) return Either.Left(UpdateFailure.InvalidInput("input index must exist in the input tx"))
        val input = inputs[inputIndex]
        return sign(priv, inputIndex, input, global).map { SignPsbtResult(this.copy(inputs = inputs.updated(inputIndex, it.first)), it.second) }
    }

    private fun sign(priv: PrivateKey, inputIndex: Int, input: Input, global: Global): Either> {
        val txIn = global.tx.txIn[inputIndex]
        return when (input) {
            is Input.PartiallySignedInputWithoutUtxo -> Either.Left(UpdateFailure.CannotSignInput(inputIndex, "cannot sign: input hasn't been updated with utxo data"))
            is Input.WitnessInput.PartiallySignedWitnessInput -> {
                if (input.nonWitnessUtxo != null && input.nonWitnessUtxo!!.txid != txIn.outPoint.txid) {
                    Either.Left(UpdateFailure.InvalidNonWitnessUtxo("non-witness utxo does not match unsigned tx input"))
                } else if (input.nonWitnessUtxo != null && input.nonWitnessUtxo!!.txOut.size <= txIn.outPoint.index) {
                    Either.Left(UpdateFailure.InvalidNonWitnessUtxo("non-witness utxo index out of bounds"))
                } else if (!Script.isNativeWitnessScript(input.txOut.publicKeyScript) && !Script.isPayToScript(input.txOut.publicKeyScript.toByteArray())) {
                    Either.Left(UpdateFailure.InvalidWitnessUtxo("witness utxo must use native segwit or P2SH embedded segwit"))
                } else {
                    signWitness(priv, inputIndex, input, global)
                }
            }
            is Input.NonWitnessInput.PartiallySignedNonWitnessInput -> {
                if (input.inputTx.txid != txIn.outPoint.txid) {
                    Either.Left(UpdateFailure.InvalidNonWitnessUtxo("non-witness utxo does not match unsigned tx input"))
                } else if (input.inputTx.txOut.size <= txIn.outPoint.index) {
                    Either.Left(UpdateFailure.InvalidNonWitnessUtxo("non-witness utxo index out of bounds"))
                } else {
                    signNonWitness(priv, inputIndex, input, global)
                }
            }
            is Input.FinalizedInputWithoutUtxo -> Either.Left(UpdateFailure.CannotSignInput(inputIndex, "cannot sign: input has already been finalized"))
            is Input.WitnessInput.FinalizedWitnessInput -> Either.Left(UpdateFailure.CannotSignInput(inputIndex, "cannot sign: input has already been finalized"))
            is Input.NonWitnessInput.FinalizedNonWitnessInput -> Either.Left(UpdateFailure.CannotSignInput(inputIndex, "cannot sign: input has already been finalized"))

        }
    }

    private fun signNonWitness(priv: PrivateKey, inputIndex: Int, input: Input.NonWitnessInput.PartiallySignedNonWitnessInput, global: Global): Either> {
        val txIn = global.tx.txIn[inputIndex]
        val redeemScript = when (input.redeemScript) {
            null -> runCatching {
                Script.parse(input.inputTx.txOut[txIn.outPoint.index.toInt()].publicKeyScript)
            }.getOrElse {
                return Either.Left(UpdateFailure.InvalidNonWitnessUtxo("failed to parse redeem script"))
            }
            else -> {
                // If a redeem script is provided in the partially signed input, the utxo must be a p2sh for that script.
                val p2sh = Script.write(Script.pay2sh(input.redeemScript))
                if (!input.inputTx.txOut[txIn.outPoint.index.toInt()].publicKeyScript.contentEquals(p2sh)) {
                    return Either.Left(UpdateFailure.InvalidNonWitnessUtxo("redeem script does not match non-witness utxo scriptPubKey"))
                } else {
                    input.redeemScript
                }
            }
        }
        val sig = ByteVector(Transaction.signInput(global.tx, inputIndex, redeemScript, input.sighashType ?: SigHash.SIGHASH_ALL, input.amount, SigVersion.SIGVERSION_BASE, priv))
        return Either.Right(Pair(input.copy(partialSigs = input.partialSigs + (priv.publicKey() to sig)), sig))
    }

    private fun signWitness(priv: PrivateKey, inputIndex: Int, input: Input.WitnessInput.PartiallySignedWitnessInput, global: Global): Either> {
        val pubkeyScript = runCatching {
            Script.parse(input.txOut.publicKeyScript)
        }.getOrElse {
            return Either.Left(UpdateFailure.InvalidWitnessUtxo("failed to parse pubkeyScript"))
        }
        return when {
            Script.isPay2wpkh(pubkeyScript) -> when (input.witnessScript) {
                null -> Either.Left(UpdateFailure.InvalidWitnessUtxo("missing witness script"))
                else -> {
                    val sig = ByteVector(Transaction.signInput(global.tx, inputIndex, input.witnessScript, input.sighashType ?: SigHash.SIGHASH_ALL, input.amount, SigVersion.SIGVERSION_WITNESS_V0, priv))
                    Either.Right(Pair(input.copy(partialSigs = input.partialSigs + (priv.publicKey() to sig)), sig))
                }
            }
            Script.isPay2wsh(pubkeyScript) -> when {
                input.witnessScript == null -> Either.Left(UpdateFailure.InvalidWitnessUtxo("missing witness script"))
                pubkeyScript != Script.pay2wsh(input.witnessScript) -> Either.Left(UpdateFailure.InvalidWitnessUtxo("witness script does not match redeemScript or scriptPubKey"))
                else -> {
                    val sig = ByteVector(Transaction.signInput(global.tx, inputIndex, input.witnessScript, input.sighashType ?: SigHash.SIGHASH_ALL, input.amount, SigVersion.SIGVERSION_WITNESS_V0, priv))
                    Either.Right(Pair(input.copy(partialSigs = input.partialSigs + (priv.publicKey() to sig)), sig))
                }
            }
            Script.isPay2tr(pubkeyScript) -> when (input.taprootInternalKey) {
                null -> Either.Left(UpdateFailure.InvalidWitnessUtxo("missing taproot internal key"))
                else -> {
                    // When spending taproot inputs, we include *all* of the transaction's inputs in the signed hash.
                    val spentOutputs = this.inputs.mapIndexedNotNull { idx, txIn -> txIn.witnessUtxo ?: txIn.nonWitnessUtxo?.txOut?.get(this.global.tx.txIn[idx].outPoint.index.toInt()) }
                    if (spentOutputs.size != this.inputs.size) {
                        Either.Left(UpdateFailure.InvalidInput("missing txOut for one of our inputs"))
                    } else {
                        val sig = Transaction.signInputTaprootKeyPath(priv, global.tx, inputIndex, spentOutputs, input.sighashType ?: SigHash.SIGHASH_DEFAULT, null)
                        val sigAndSighashType = input.sighashType?.let { sig.concat(it.toByte()) } ?: sig
                        Either.Right(Pair(input.copy(taprootKeySignature = sigAndSighashType), sigAndSighashType))
                    }
                }
            }
            Script.isPay2sh(pubkeyScript) -> when {
                input.redeemScript == null -> Either.Left(UpdateFailure.InvalidWitnessUtxo("missing redeem script"))
                pubkeyScript != Script.pay2sh(input.redeemScript) -> Either.Left(UpdateFailure.InvalidWitnessUtxo("redeem script does not match witness utxo scriptPubKey"))
                else -> when {
                    input.witnessScript == null -> {
                        val sig = ByteVector(Transaction.signInput(global.tx, inputIndex, input.redeemScript, input.sighashType ?: SigHash.SIGHASH_ALL, input.amount, SigVersion.SIGVERSION_WITNESS_V0, priv))
                        Either.Right(Pair(input.copy(partialSigs = input.partialSigs + (priv.publicKey() to sig)), sig))
                    }
                    !Script.isPay2wpkh(input.redeemScript) && input.redeemScript != Script.pay2wsh(input.witnessScript) -> {
                        Either.Left(UpdateFailure.InvalidWitnessUtxo("witness script does not match redeemScript or scriptPubKey"))
                    }
                    else -> {
                        val sig = ByteVector(Transaction.signInput(global.tx, inputIndex, input.witnessScript, input.sighashType ?: SigHash.SIGHASH_ALL, input.amount, SigVersion.SIGVERSION_WITNESS_V0, priv))
                        Either.Right(Pair(input.copy(partialSigs = input.partialSigs + (priv.publicKey() to sig)), sig))
                    }
                }
            }
            else -> {
                val script = input.witnessScript ?: input.redeemScript ?: pubkeyScript
                val sig = ByteVector(Transaction.signInput(global.tx, inputIndex, script, input.sighashType ?: SigHash.SIGHASH_ALL, input.amount, SigVersion.SIGVERSION_WITNESS_V0, priv))
                Either.Right(Pair(input.copy(partialSigs = input.partialSigs + (priv.publicKey() to sig)), sig))
            }
        }
    }

    /**
     * Implements the PSBT finalizer role: finalizes a given segwit input.
     * This will clear all fields from the input except the utxo, scriptSig, scriptWitness and unknown entries.
     *
     * @param outPoint input that should be finalized.
     * @param scriptWitness witness script.
     * @return a psbt with the given input finalized.
     */
    public fun finalizeWitnessInput(outPoint: OutPoint, scriptWitness: ScriptWitness): Either {
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outPoint }
        if (inputIndex < 0) return Either.Left(UpdateFailure.InvalidInput("psbt transaction does not spend the provided outpoint"))
        return finalizeWitnessInput(inputIndex, scriptWitness)
    }

    /**
     * Implements the PSBT finalizer role: finalizes a given segwit input.
     * This will clear all fields from the input except the utxo, scriptSig, scriptWitness and unknown entries.
     *
     * @param inputIndex index of the input that should be finalized.
     * @param scriptWitness witness script.
     * @return a psbt with the given input finalized.
     */
    public fun finalizeWitnessInput(inputIndex: Int, scriptWitness: ScriptWitness): Either {
        if (inputIndex >= inputs.size) return Either.Left(UpdateFailure.InvalidInput("input index must exist in the input tx"))
        return when (val input = inputs[inputIndex]) {
            is Input.PartiallySignedInputWithoutUtxo -> Either.Left(UpdateFailure.CannotFinalizeInput(inputIndex, "cannot finalize: input is missing utxo details"))
            is Input.NonWitnessInput.PartiallySignedNonWitnessInput -> Either.Left(UpdateFailure.CannotFinalizeInput(inputIndex, "cannot finalize: input is a non-segwit input"))
            is Input.WitnessInput.PartiallySignedWitnessInput -> {
                val scriptSig = input.redeemScript?.let { script -> listOf(OP_PUSHDATA(Script.write(script))) } // p2sh-embedded segwit
                val finalizedInput = Input.WitnessInput.FinalizedWitnessInput(input.txOut, input.nonWitnessUtxo, scriptWitness, scriptSig, input.ripemd160, input.sha256, input.hash160, input.hash256, input.unknown)
                Either.Right(this.copy(inputs = this.inputs.updated(inputIndex, finalizedInput)))
            }
            else -> Either.Left(UpdateFailure.CannotFinalizeInput(inputIndex, ("cannot finalize: input has already been finalized")))
        }
    }

    /**
     * Implements the PSBT finalizer role: finalizes a given non-segwit input.
     * This will clear all fields from the input except the utxo, scriptSig and unknown entries.
     *
     * @param outPoint input that should be finalized.
     * @param scriptSig signature script.
     * @return a psbt with the given input finalized.
     */
    public fun finalizeNonWitnessInput(outPoint: OutPoint, scriptSig: List): Either {
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outPoint }
        if (inputIndex < 0) return Either.Left(UpdateFailure.InvalidInput("psbt transaction does not spend the provided outpoint"))
        return finalizeNonWitnessInput(inputIndex, scriptSig)
    }

    /**
     * Implements the PSBT finalizer role: finalizes a given non-segwit input.
     * This will clear all fields from the input except the utxo, scriptSig and unknown entries.
     *
     * @param inputIndex index of the input that should be finalized.
     * @param scriptSig signature script.
     * @return a psbt with the given input finalized.
     */
    public fun finalizeNonWitnessInput(inputIndex: Int, scriptSig: List): Either {
        if (inputIndex >= inputs.size) return Either.Left(UpdateFailure.InvalidInput("input index must exist in the input tx"))
        return when (val input = inputs[inputIndex]) {
            is Input.PartiallySignedInputWithoutUtxo -> Either.Left(UpdateFailure.CannotFinalizeInput(inputIndex, "cannot finalize: input is missing utxo details"))
            is Input.WitnessInput.PartiallySignedWitnessInput -> Either.Left(UpdateFailure.CannotFinalizeInput(inputIndex, "cannot finalize: input is a segwit input"))
            is Input.NonWitnessInput.PartiallySignedNonWitnessInput -> {
                val finalizedInput = Input.NonWitnessInput.FinalizedNonWitnessInput(input.inputTx, input.outputIndex, scriptSig, input.ripemd160, input.sha256, input.hash160, input.hash256, input.unknown)
                Either.Right(this.copy(inputs = this.inputs.updated(inputIndex, finalizedInput)))
            }
            else -> Either.Left(UpdateFailure.CannotFinalizeInput(inputIndex, ("cannot finalize: input has already been finalized")))
        }
    }

    /**
     * Implements the PSBT extractor role: extracts a valid transaction from the psbt data.
     *
     * @return a fully signed, ready-to-broadcast transaction.
     */
    public fun extract(): Either {
        val (finalTxsIn, utxos) = global.tx.txIn.zip(inputs).map { (txIn, input) ->
            val finalTxIn = txIn.copy(
                witness = input.scriptWitness ?: ScriptWitness.empty,
                signatureScript = input.scriptSig?.let { ByteVector(Script.write(it)) } ?: ByteVector.empty
            )
            val utxo = when (input) {
                is Input.NonWitnessInput.FinalizedNonWitnessInput -> {
                    if (input.inputTx.txid != txIn.outPoint.txid) return Either.Left(UpdateFailure.CannotExtractTx("non-witness utxo does not match unsigned tx input"))
                    if (input.inputTx.txOut.size <= txIn.outPoint.index) return Either.Left(UpdateFailure.CannotExtractTx("non-witness utxo index out of bounds"))
                    input.inputTx.txOut[txIn.outPoint.index.toInt()]
                }
                is Input.WitnessInput.FinalizedWitnessInput -> input.txOut
                else -> return Either.Left(UpdateFailure.CannotExtractTx("some utxos are missing"))
            }
            Pair(finalTxIn, txIn.outPoint to utxo)
        }.unzip()
        val finalTx = global.tx.copy(txIn = finalTxsIn)
        return try {
            Transaction.correctlySpends(finalTx, utxos.toMap(), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
            Either.Right(finalTx)
        } catch (e: Exception) {
            Either.Left(UpdateFailure.CannotExtractTx("extracted transaction doesn't pass standard script validation"))
        }
    }

    /**
     * Compute the fees paid by the PSBT.
     * Note that if some inputs have not been updated yet, the fee cannot be computed.
     */
    public fun computeFees(): Satoshi? {
        val inputAmounts = inputs.map { input ->
            when (input) {
                is Input.WitnessInput -> input.amount
                is Input.NonWitnessInput -> input.amount
                else -> null
            }
        }
        return when {
            inputAmounts.any { it == null } -> null
            else -> {
                val amountOut = global.tx.txOut.sumOf { it.amount.sat }.toSatoshi()
                val amountIn = inputAmounts.filterNotNull().sumOf { it.sat }.toSatoshi()
                amountIn - amountOut
            }
        }
    }

    public fun getInput(outPoint: OutPoint): Input? {
        val inputIndex = global.tx.txIn.indexOfFirst { it.outPoint == outPoint }
        return if (inputIndex >= 0) inputs[inputIndex] else null
    }

    public fun getInput(inputIndex: Int): Input? {
        return if (0 <= inputIndex && inputIndex < inputs.size) inputs[inputIndex] else null
    }

    public companion object {

        /** Only version 0 is supported for now. */
        public const val Version: Long = 0

        /**
         * Implements the PSBT combiner role: combines multiple psbts for the same unsigned transaction.
         *
         * @param psbts partially signed bitcoin transactions to combine.
         * @return a psbt that contains data from all the input psbts.
         */
        @JvmStatic
        public fun combine(vararg psbts: Psbt): Either {
            return when {
                psbts.map { it.global.tx.txid }.toSet().size != 1 -> Either.Left(UpdateFailure.CannotCombine("cannot combine psbts for distinct transactions"))
                psbts.map { it.inputs.size }.toSet() != setOf(psbts[0].global.tx.txIn.size) -> Either.Left(UpdateFailure.CannotCombine("some psbts have an invalid number of inputs"))
                psbts.map { it.outputs.size }.toSet() != setOf(psbts[0].global.tx.txOut.size) -> Either.Left(UpdateFailure.CannotCombine("some psbts have an invalid number of outputs"))
                else -> {
                    val global = psbts[0].global.copy(
                        unknown = combineUnknown(psbts.map { it.global.unknown }),
                        extendedPublicKeys = combineExtendedPublicKeys(psbts.map { it.global.extendedPublicKeys })
                    )
                    val combined = Psbt(
                        global,
                        global.tx.txIn.indices.map { i -> combineInput(global.tx.txIn[i], psbts.map { it.inputs[i] }) },
                        global.tx.txOut.indices.map { i -> combineOutput(psbts.map { it.outputs[i] }) }
                    )
                    Either.Right(combined)
                }
            }
        }

        private fun combineUnknown(unknowns: List>): List = unknowns.flatten().associateBy { it.key }.values.toList()

        private fun combineExtendedPublicKeys(keys: List>): List = keys.flatten().associateBy { it.extendedPublicKey }.values.toList()

        private fun combineInput(txIn: TxIn, inputs: List): Input = createInput(
            txIn,
            inputs.firstNotNullOfOrNull { it.nonWitnessUtxo },
            inputs.firstNotNullOfOrNull { it.witnessUtxo },
            inputs.firstNotNullOfOrNull { it.sighashType },
            inputs.flatMap { it.partialSigs.toList() }.toMap(),
            inputs.flatMap { it.derivationPaths.toList() }.toMap(),
            inputs.firstNotNullOfOrNull { it.redeemScript },
            inputs.firstNotNullOfOrNull { it.witnessScript },
            inputs.firstNotNullOfOrNull { it.scriptSig },
            inputs.firstNotNullOfOrNull { it.scriptWitness },
            inputs.flatMap { it.ripemd160 }.toSet(),
            inputs.flatMap { it.sha256 }.toSet(),
            inputs.flatMap { it.hash160 }.toSet(),
            inputs.flatMap { it.hash256 }.toSet(),
            inputs.firstNotNullOfOrNull { it.taprootKeySignature },
            inputs.flatMap { it.taprootDerivationPaths.toList() }.toMap(),
            inputs.firstNotNullOfOrNull { it.taprootInternalKey },
            combineUnknown(inputs.map { it.unknown })
        )

        private fun combineOutput(outputs: List): Output = createOutput(
            outputs.firstNotNullOfOrNull { it.redeemScript },
            outputs.firstNotNullOfOrNull { it.witnessScript },
            outputs.flatMap { it.derivationPaths.toList() }.toMap(),
            outputs.firstNotNullOfOrNull { it.taprootInternalKey },
            outputs.flatMap { it.taprootDerivationPaths.toList() }.toMap(),
            combineUnknown(outputs.map { it.unknown })
        )

        /**
         * Joins multiple distinct PSBTs with different inputs and outputs into one PSBT with inputs and outputs from all of
         * the PSBTs. No input in any of the PSBTs can be in more than one of the PSBTs.
         *
         * @param psbts partially signed bitcoin transactions to join.
         * @return a psbt that contains data from all the input psbts.
         */
        @JvmStatic
        public fun join(vararg psbts: Psbt): Either {
            return when {
                psbts.isEmpty() -> Either.Left(UpdateFailure.CannotJoin("no psbt provided"))
                psbts.map { it.global.version }.toSet().size != 1 -> Either.Left(UpdateFailure.CannotJoin("cannot join psbts with different versions"))
                psbts.map { it.global.tx.version }.toSet().size != 1 -> Either.Left(UpdateFailure.CannotJoin("cannot join psbts with different tx versions"))
                psbts.map { it.global.tx.lockTime }.toSet().size != 1 -> Either.Left(UpdateFailure.CannotJoin("cannot join psbts with different tx lockTime"))
                psbts.any { it.global.tx.txIn.size != it.inputs.size || it.global.tx.txOut.size != it.outputs.size } -> Either.Left(UpdateFailure.CannotJoin("some psbts have an invalid number of inputs/outputs"))
                psbts.flatMap { it.global.tx.txIn.map { txIn -> txIn.outPoint } }.toSet().size != psbts.sumOf { it.global.tx.txIn.size } -> Either.Left(UpdateFailure.CannotJoin("cannot join psbts that spend the same input"))
                else -> {
                    val global = psbts[0].global.copy(
                        tx = psbts[0].global.tx.copy(
                            txIn = psbts.flatMap { it.global.tx.txIn },
                            txOut = psbts.flatMap { it.global.tx.txOut }
                        ),
                        extendedPublicKeys = psbts.flatMap { it.global.extendedPublicKeys }.distinct(),
                        unknown = psbts.flatMap { it.global.unknown }.distinct()
                    )
                    Either.Right(psbts[0].copy(
                        global = global,
                        inputs = psbts.flatMap { it.inputs },
                        outputs = psbts.flatMap { it.outputs }
                    ))
                }
            }
        }

        @JvmStatic
        public fun write(psbt: Psbt): ByteVector {
            val output = ByteArrayOutput()
            write(psbt, output)
            return ByteVector(output.toByteArray())
        }

        @JvmStatic
        public fun write(psbt: Psbt, out: fr.acinq.bitcoin.io.Output) {
            /********** Magic header **********/
            out.write(0x70)
            out.write(0x73)
            out.write(0x62)
            out.write(0x74)
            out.write(0xff)

            /********** Global types **********/
            writeDataEntry(DataEntry(ByteVector("00"), ByteVector(Transaction.write(psbt.global.tx, Protocol.PROTOCOL_VERSION or Transaction.SERIALIZE_TRANSACTION_NO_WITNESS))), out)
            psbt.global.extendedPublicKeys.forEach { xpub ->
                val key = ByteArrayOutput()
                key.write(0x01) // 
                Pack.writeInt32BE(xpub.prefix.toInt(), key)
                DeterministicWallet.write(xpub.extendedPublicKey, key)
                val value = ByteArrayOutput()
                Pack.writeInt32BE(xpub.masterKeyFingerprint.toInt(), value)
                xpub.extendedPublicKey.path.path.forEach { child -> Pack.writeInt32LE(child.toInt(), value) }
                writeDataEntry(DataEntry(ByteVector(key.toByteArray()), ByteVector(value.toByteArray())), out)
            }
            if (psbt.global.version > 0) {
                writeDataEntry(DataEntry(ByteVector("fb"), ByteVector(Pack.writeInt32LE(psbt.global.version.toInt()))), out)
            }
            psbt.global.unknown.forEach { writeDataEntry(it, out) }
            out.write(0x00) // separator

            /********** Inputs **********/
            psbt.inputs.forEach { input ->
                input.nonWitnessUtxo?.let { writeDataEntry(DataEntry(ByteVector("00"), ByteVector(Transaction.write(it))), out) }
                input.witnessUtxo?.let { writeDataEntry(DataEntry(ByteVector("01"), ByteVector(TxOut.write(it))), out) }
                sortPublicKeys(input.partialSigs).forEach { (publicKey, signature) -> writeDataEntry(DataEntry(ByteVector("02") + publicKey.value, signature), out) }
                input.sighashType?.let { writeDataEntry(DataEntry(ByteVector("03"), ByteVector(Pack.writeInt32LE(it))), out) }
                input.redeemScript?.let { writeDataEntry(DataEntry(ByteVector("04"), ByteVector(Script.write(it))), out) }
                input.witnessScript?.let { writeDataEntry(DataEntry(ByteVector("05"), ByteVector(Script.write(it))), out) }
                sortPublicKeys(input.derivationPaths).forEach { (publicKey, path) ->
                    val key = ByteVector("06") + publicKey.value
                    val value = ByteVector(Pack.writeInt32BE(path.masterKeyFingerprint.toInt())).concat(path.keyPath.path.map { ByteVector(Pack.writeInt32LE(it.toInt())) })
                    writeDataEntry(DataEntry(key, value), out)
                }
                input.scriptSig?.let { writeDataEntry(DataEntry(ByteVector("07"), ByteVector(Script.write(it))), out) }
                input.scriptWitness?.let { writeDataEntry(DataEntry(ByteVector("08"), ByteVector(ScriptWitness.write(it))), out) }
                input.ripemd160.forEach { writeDataEntry(DataEntry(ByteVector("0a") + Crypto.ripemd160(it), it), out) }
                input.sha256.forEach { writeDataEntry(DataEntry(ByteVector("0b") + Crypto.sha256(it), it), out) }
                input.hash160.forEach { writeDataEntry(DataEntry(ByteVector("0c") + Crypto.hash160(it), it), out) }
                input.hash256.forEach { writeDataEntry(DataEntry(ByteVector("0d") + Crypto.hash256(it), it), out) }
                input.taprootKeySignature?.let { writeDataEntry(DataEntry(ByteVector("13"), it), out) }
                sortXonlyPublicKeys(input.taprootDerivationPaths).forEach { (publicKey, path) ->
                    val key = ByteVector("16") + publicKey.value
                    val value = path.write().byteVector()
                    writeDataEntry(DataEntry(key, value), out)
                }
                input.taprootInternalKey?.let { writeDataEntry(DataEntry(ByteVector("17"), it.value), out) }
                input.unknown.forEach { writeDataEntry(it, out) }
                out.write(0x00) // separator
            }

            /********** Outputs **********/
            psbt.outputs.forEach { output ->
                output.redeemScript?.let { writeDataEntry(DataEntry(ByteVector("00"), ByteVector(Script.write(it))), out) }
                output.witnessScript?.let { writeDataEntry(DataEntry(ByteVector("01"), ByteVector(Script.write(it))), out) }
                sortPublicKeys(output.derivationPaths).forEach { (publicKey, path) ->
                    val key = ByteVector("02") + publicKey.value
                    val value = ByteVector(Pack.writeInt32BE(path.masterKeyFingerprint.toInt())).concat(path.keyPath.path.map { ByteVector(Pack.writeInt32LE(it.toInt())) })
                    writeDataEntry(DataEntry(key, value), out)
                }
                output.taprootInternalKey?.let { writeDataEntry(DataEntry(ByteVector("05"), it.value), out) }
                sortXonlyPublicKeys(output.taprootDerivationPaths).forEach { (publicKey, path) ->
                    val key = ByteVector("07") + publicKey.value
                    val value = path.write().byteVector()
                    writeDataEntry(DataEntry(key, value), out)
                }
                output.unknown.forEach { writeDataEntry(it, out) }
                out.write(0x00) // separator
            }
        }

        /** We use lexicographic ordering on the public keys. */
        private fun  sortPublicKeys(publicKeys: Map): List> {
            return publicKeys.toList().sortedWith { a, b -> LexicographicalOrdering.compare(a.first, b.first) }
        }

        /** We use lexicographic ordering on the public keys. */
        private fun  sortXonlyPublicKeys(publicKeys: Map): List> {
            return publicKeys.toList().sortedWith { a, b -> LexicographicalOrdering.compare(a.first, b.first) }
        }

        private fun writeDataEntry(entry: DataEntry, output: fr.acinq.bitcoin.io.Output) {
            BtcSerializer.writeVarint(entry.key.size(), output)
            output.write(entry.key.bytes)
            BtcSerializer.writeVarint(entry.value.size(), output)
            output.write(entry.value.bytes)
        }

        @JvmStatic
        public fun read(input: ByteVector): Either = read(ByteArrayInput(input.toByteArray()))

        @JvmStatic
        public fun read(input: ByteArray): Either = read(ByteArrayInput(input))

        @JvmStatic
        public fun read(input: fr.acinq.bitcoin.io.Input): Either {
            /********** Magic header **********/
            if (input.read() != 0x70 || input.read() != 0x73 || input.read() != 0x62 || input.read() != 0x74) {
                return Either.Left(ParseFailure.InvalidMagicBytes)
            }
            if (input.read() != 0xff) {
                return Either.Left(ParseFailure.InvalidSeparator)
            }

            /********** Global types **********/
            val global = run {
                val globalMap = readDataMap(input).getOrElse {
                    return when (it) {
                        is ReadEntryFailure.DuplicateKeys -> Either.Left(ParseFailure.DuplicateKeys)
                        else -> Either.Left(ParseFailure.InvalidContent)
                    }
                }
                val keyTypes = setOf(0x00.toByte(), 0x01.toByte(), 0xfb.toByte())
                val (known, unknown) = globalMap.partition { keyTypes.contains(it.key[0]) }
                val version = known.find { it.key[0] == 0xfb.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidPsbtVersion("version key must contain exactly 1 byte"))
                        it.value.size() != 4 -> return Either.Left(ParseFailure.InvalidPsbtVersion("version must contain exactly 4 bytes"))
                        else -> {
                            val v = Pack.int32LE(it.value.bytes).toUInt().toLong()
                            when {
                                v > Version -> return Either.Left(ParseFailure.UnsupportedPsbtVersion(v))
                                else -> v
                            }
                        }
                    }
                } ?: 0L
                val tx = known.find { it.key[0] == 0x00.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidGlobalTx("global tx key must contain exactly 1 byte"))
                        else -> {
                            val tx = try {
                                Transaction.read(it.value.bytes, Protocol.PROTOCOL_VERSION or Transaction.SERIALIZE_TRANSACTION_NO_WITNESS)
                            } catch (e: Exception) {
                                return Either.Left(ParseFailure.InvalidGlobalTx(e.message ?: "failed to parse transaction"))
                            }
                            when {
                                tx.txIn.any { input -> input.hasWitness || !input.signatureScript.isEmpty() } -> return Either.Left(ParseFailure.InvalidGlobalTx("global tx inputs must have empty scriptSigs and witness"))
                                else -> tx
                            }
                        }
                    }
                } ?: return Either.Left(ParseFailure.GlobalTxMissing)
                val xpubs = known.filter { it.key[0] == 0x01.toByte() }.map {
                    when {
                        it.key.size() != 79 -> return Either.Left(ParseFailure.InvalidExtendedPublicKey(" must contain 78 bytes"))
                        else -> {
                            val xpub = ByteArrayInput(it.key.drop(1).toByteArray())
                            val prefix = Pack.int32BE(xpub).toUInt().toLong()
                            val depth = xpub.read()
                            val parent = Pack.int32BE(xpub).toUInt().toLong()
                            val childNumber = Pack.int32BE(xpub).toUInt().toLong()
                            val chainCode = ByteVector32(xpub.readNBytes(32)!!)
                            val publicKey = ByteVector(xpub.readNBytes(33)!!)
                            when {
                                it.value.size() != 4 * (depth + 1) -> return Either.Left(ParseFailure.InvalidExtendedPublicKey(" must contain the master key fingerprint and derivation path"))
                                else -> {
                                    val masterKeyFingerprint = Pack.int32BE(it.value.take(4).toByteArray()).toUInt().toLong()
                                    val derivationPath = KeyPath((0 until depth).map { i -> Pack.int32LE(it.value.slice(4 * (i + 1), 4 * (i + 2)).toByteArray()).toUInt().toLong() })
                                    when {
                                        derivationPath.lastChildNumber != childNumber -> return Either.Left(ParseFailure.InvalidExtendedPublicKey(" last child number mismatch"))
                                        else -> ExtendedPublicKeyWithMaster(prefix, masterKeyFingerprint, DeterministicWallet.ExtendedPublicKey(publicKey, chainCode, depth, derivationPath, parent))
                                    }
                                }
                            }
                        }
                    }
                }
                Global(version, tx, xpubs, unknown)
            }

            /********** Inputs **********/
            val inputs = global.tx.txIn.map { txIn ->
                val keyTypes = setOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x13, 0x16, 0x17, 0x0a, 0x0b, 0x0c, 0x0d)
                val entries = readDataMap(input).getOrElse {
                    return when (it) {
                        is ReadEntryFailure.DuplicateKeys -> Either.Left(ParseFailure.DuplicateKeys)
                        else -> Either.Left(ParseFailure.InvalidContent)
                    }
                }
                val (known, unknown) = entries.partition { keyTypes.contains(it.key[0]) }
                val nonWitnessUtxo = known.find { it.key[0] == 0x00.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("non-witness utxo key must contain exactly 1 byte"))
                        else -> {
                            val inputTx = try {
                                Transaction.read(it.value.bytes)
                            } catch (e: Exception) {
                                return Either.Left(ParseFailure.InvalidTxInput(e.message ?: "failed to parse transaction"))
                            }
                            when {
                                inputTx.txid != txIn.outPoint.txid || txIn.outPoint.index >= inputTx.txOut.size -> return Either.Left(ParseFailure.InvalidTxInput("non-witness utxo does not match psbt outpoint"))
                                else -> inputTx
                            }
                        }
                    }
                }
                val witnessUtxo = known.find { it.key[0] == 0x01.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("witness utxo key must contain exactly 1 byte"))
                        else -> {
                            val txOut = try {
                                TxOut.read(it.value.bytes)
                            } catch (e: Exception) {
                                return Either.Left(ParseFailure.InvalidTxInput(e.message ?: "failed to parse transaction output"))
                            }
                            nonWitnessUtxo?.let { tx -> if (tx.txOut[txIn.outPoint.index.toInt()] != txOut) return Either.Left(ParseFailure.InvalidTxInput("witness utxo does not match non-witness utxo output")) }
                            txOut
                        }
                    }
                }
                val partialSigs = known.filter { it.key[0] == 0x02.toByte() }.map {
                    when {
                        it.key.size() != 34 -> return Either.Left(ParseFailure.InvalidTxInput("public key must contain exactly 33 bytes"))
                        else -> PublicKey(it.key.drop(1)) to it.value
                    }
                }.toMap()
                val sighashType = known.find { it.key[0] == 0x03.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("sighash type key must contain exactly 1 byte"))
                        it.value.size() != 4 -> return Either.Left(ParseFailure.InvalidTxInput("sighash type must contain exactly 4 bytes"))
                        else -> Pack.int32LE(it.value.bytes)
                    }
                }
                val redeemScript = known.find { it.key[0] == 0x04.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("redeem script key must contain exactly 1 byte"))
                        else -> runCatching { Script.parse(it.value) }.getOrElse { return Either.Left(ParseFailure.InvalidTxInput("failed to parse redeem script")) }
                    }
                }
                val witnessScript = known.find { it.key[0] == 0x05.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("witness script key must contain exactly 1 byte"))
                        else -> runCatching { Script.parse(it.value) }.getOrElse { return Either.Left(ParseFailure.InvalidTxInput("failed to parse witness script")) }
                    }
                }
                val derivationPaths = known.filter { it.key[0] == 0x06.toByte() }.map {
                    when {
                        it.key.size() != 34 -> return Either.Left(ParseFailure.InvalidTxInput("bip32 derivation public key must contain exactly 33 bytes"))
                        it.value.size() < 4 || it.value.size() % 4 != 0 -> return Either.Left(ParseFailure.InvalidTxInput("bip32 derivation must contain master key fingerprint and child indexes"))
                        else -> {
                            val publicKey = PublicKey(it.key.drop(1))
                            val masterKeyFingerprint = Pack.int32BE(it.value.take(4).toByteArray()).toUInt().toLong()
                            val childCount = (it.value.size() / 4) - 1
                            val derivationPath = KeyPath((0 until childCount).map { i -> Pack.int32LE(it.value.slice(4 * (i + 1), 4 * (i + 2)).toByteArray()).toUInt().toLong() })
                            publicKey to KeyPathWithMaster(masterKeyFingerprint, derivationPath)
                        }
                    }
                }.toMap()
                val scriptSig = known.find { it.key[0] == 0x07.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("script sig key must contain exactly 1 byte"))
                        else -> runCatching { Script.parse(it.value) }.getOrElse { return Either.Left(ParseFailure.InvalidTxInput("failed to parse script sig")) }
                    }
                }
                val scriptWitness = known.find { it.key[0] == 0x08.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("script witness key must contain exactly 1 byte"))
                        else -> try {
                            ScriptWitness.read(it.value.bytes)
                        } catch (e: Exception) {
                            return Either.Left(ParseFailure.InvalidTxInput(e.message ?: "failed to parse script witness"))
                        }
                    }
                }
                val taprootKeySignature = known.find { it.key[0] == 0x13.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("taproot keypath signature key must contain exactly 1 byte"))
                        it.value.size() != 64 && it.value.size() != 65 -> return Either.Left(ParseFailure.InvalidTxInput("taproot keypath signature must contain 64 or 65 bytes"))
                        else -> it.value
                    }
                }
                val taprootDerivationPaths = known.filter { it.key[0] == 0x16.toByte() }.map {
                    when {
                        it.key.size() != 33 -> return Either.Left(ParseFailure.InvalidTxInput("taproot derivation path key must contain exactly 32 bytes"))
                        else -> {
                            val xonlyPublicKey = XonlyPublicKey(it.key.drop(1).toByteArray().byteVector32())
                            val path = TaprootBip32DerivationPath.read(it.value.toByteArray())
                            xonlyPublicKey to path
                        }
                    }
                }.toMap()
                val taprootInternalKey = known.find { it.key[0] == 0x17.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxInput("taproot internal key entry must have an empty key"))
                        it.value.size() != 32 -> return Either.Left(ParseFailure.InvalidTxInput("taproot internal key entry must have a 32 bytes value"))
                        else -> XonlyPublicKey(it.value.toByteArray().byteVector32())
                    }
                }
                val ripemd160Preimages = known.filter { it.key[0] == 0x0a.toByte() }.map {
                    when {
                        it.key.size() != 21 -> return Either.Left(ParseFailure.InvalidTxInput("ripemd160 hash must contain exactly 20 bytes"))
                        !it.key.drop(1).contentEquals(Crypto.ripemd160(it.value)) -> return Either.Left(ParseFailure.InvalidTxInput("invalid ripemd160 preimage"))
                        else -> it.value
                    }
                }.toSet()
                val sha256Preimages = known.filter { it.key[0] == 0x0b.toByte() }.map {
                    when {
                        it.key.size() != 33 -> return Either.Left(ParseFailure.InvalidTxInput("sha256 hash must contain exactly 32 bytes"))
                        !it.key.drop(1).contentEquals(Crypto.sha256(it.value)) -> return Either.Left(ParseFailure.InvalidTxInput("invalid sha256 preimage"))
                        else -> it.value
                    }
                }.toSet()
                val hash160Preimages = known.filter { it.key[0] == 0x0c.toByte() }.map {
                    when {
                        it.key.size() != 21 -> return Either.Left(ParseFailure.InvalidTxInput("hash160 hash must contain exactly 20 bytes"))
                        !it.key.drop(1).contentEquals(Crypto.hash160(it.value)) -> return Either.Left(ParseFailure.InvalidTxInput("invalid hash160 preimage"))
                        else -> it.value
                    }
                }.toSet()
                val hash256Preimages = known.filter { it.key[0] == 0x0d.toByte() }.map {
                    when {
                        it.key.size() != 33 -> return Either.Left(ParseFailure.InvalidTxInput("hash256 hash must contain exactly 32 bytes"))
                        !it.key.drop(1).contentEquals(Crypto.hash256(it.value)) -> return Either.Left(ParseFailure.InvalidTxInput("invalid hash256 preimage"))
                        else -> it.value
                    }
                }.toSet()
                createInput(
                    txIn,
                    nonWitnessUtxo,
                    witnessUtxo,
                    sighashType,
                    partialSigs,
                    derivationPaths,
                    redeemScript,
                    witnessScript,
                    scriptSig,
                    scriptWitness,
                    ripemd160Preimages,
                    sha256Preimages,
                    hash160Preimages,
                    hash256Preimages,
                    taprootKeySignature,
                    taprootDerivationPaths,
                    taprootInternalKey,
                    unknown
                )
            }

            /********** Outputs **********/
            val outputs = global.tx.txOut.map {
                val keyTypes = setOf(0x00, 0x01, 0x02, 0x05, 0x07)
                val entries = readDataMap(input).getOrElse {
                    return when (it) {
                        is ReadEntryFailure.DuplicateKeys -> Either.Left(ParseFailure.DuplicateKeys)
                        else -> Either.Left(ParseFailure.InvalidContent)
                    }
                }
                val (known, unknown) = entries.partition { keyTypes.contains(it.key[0]) }
                val redeemScript = known.find { it.key[0] == 0x00.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxOutput("redeem script key must contain exactly 1 byte"))
                        else -> runCatching { Script.parse(it.value) }.getOrElse { return Either.Left(ParseFailure.InvalidTxOutput("failed to parse redeem script")) }
                    }
                }
                val witnessScript = known.find { it.key[0] == 0x01.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxOutput("witness script key must contain exactly 1 byte"))
                        else -> runCatching { Script.parse(it.value) }.getOrElse { return Either.Left(ParseFailure.InvalidTxOutput("failed to parse witness script")) }
                    }
                }
                val derivationPaths = known.filter { it.key[0] == 0x02.toByte() }.map {
                    when {
                        it.key.size() != 34 -> return Either.Left(ParseFailure.InvalidTxOutput("bip32 derivation public key must contain exactly 33 bytes"))
                        it.value.size() < 4 || it.value.size() % 4 != 0 -> return Either.Left(ParseFailure.InvalidTxOutput("bip32 derivation must contain master key fingerprint and child indexes"))
                        else -> {
                            val publicKey = PublicKey(it.key.drop(1))
                            val masterKeyFingerprint = Pack.int32BE(it.value.take(4).toByteArray()).toUInt().toLong()
                            val childCount = (it.value.size() / 4) - 1
                            val derivationPath = KeyPath((0 until childCount).map { i -> Pack.int32LE(it.value.slice(4 * (i + 1), 4 * (i + 2)).toByteArray()).toUInt().toLong() })
                            publicKey to KeyPathWithMaster(masterKeyFingerprint, derivationPath)
                        }
                    }
                }.toMap()
                val taprootInternalKey = known.find { it.key[0] == 0x05.toByte() }?.let {
                    when {
                        it.key.size() != 1 -> return Either.Left(ParseFailure.InvalidTxOutput("taproot internal key entry must have an empty key"))
                        it.value.size() != 32 -> return Either.Left(ParseFailure.InvalidTxOutput("taproot internal key entry must have a 32 bytes value"))
                        else -> XonlyPublicKey(it.value.toByteArray().byteVector32())
                    }
                }
                val taprootDerivationPaths = known.filter { it.key[0] == 0x07.toByte() }.map {
                    when {
                        it.key.size() != 33 -> return Either.Left(ParseFailure.InvalidTxOutput("taproot derivation path key must contain exactly 32 bytes"))
                        else -> {
                            val xonlyPublicKey = XonlyPublicKey(it.key.drop(1).toByteArray().byteVector32())
                            val path = TaprootBip32DerivationPath.read(it.value.toByteArray())
                            xonlyPublicKey to path
                        }
                    }
                }.toMap()
                createOutput(redeemScript, witnessScript, derivationPaths, taprootInternalKey, taprootDerivationPaths, unknown)
            }

            return if (input.availableBytes != 0) {
                Either.Left(ParseFailure.InvalidContent)
            } else {
                Either.Right(Psbt(global, inputs, outputs))
            }
        }

        private fun createInput(
            txIn: TxIn,
            nonWitnessUtxo: Transaction?,
            witnessUtxo: TxOut?,
            sighashType: Int?,
            partialSigs: Map,
            derivationPaths: Map,
            redeemScript: List?,
            witnessScript: List?,
            scriptSig: List?,
            scriptWitness: ScriptWitness?,
            ripemd160: Set,
            sha256: Set,
            hash160: Set,
            hash256: Set,
            taprootKeySignature: ByteVector?,
            taprootDerivationPaths: Map,
            taprootInternalKey: XonlyPublicKey?,
            unknown: List
        ): Input {
            val emptied = redeemScript == null && witnessScript == null && partialSigs.isEmpty() && derivationPaths.isEmpty() && sighashType == null
            return when {
                // @formatter:off
                // If the input is finalized, it must have been emptied otherwise it's invalid.
                witnessUtxo != null && scriptWitness != null && emptied -> Input.WitnessInput.FinalizedWitnessInput(witnessUtxo, nonWitnessUtxo, scriptWitness, scriptSig, ripemd160, sha256, hash160, hash256, unknown)
                nonWitnessUtxo != null && scriptSig != null && emptied -> Input.NonWitnessInput.FinalizedNonWitnessInput(nonWitnessUtxo, txIn.outPoint.index.toInt(), scriptSig, ripemd160, sha256, hash160, hash256, unknown)
                (scriptSig != null || scriptWitness != null) && emptied -> Input.FinalizedInputWithoutUtxo(scriptWitness, scriptSig, ripemd160, sha256, hash160, hash256, unknown)
                witnessUtxo != null -> Input.WitnessInput.PartiallySignedWitnessInput(witnessUtxo, nonWitnessUtxo, sighashType, partialSigs, derivationPaths, redeemScript, witnessScript, ripemd160, sha256, hash160, hash256, taprootKeySignature, taprootDerivationPaths, taprootInternalKey, unknown)
                nonWitnessUtxo != null -> Input.NonWitnessInput.PartiallySignedNonWitnessInput(nonWitnessUtxo, txIn.outPoint.index.toInt(), sighashType, partialSigs, derivationPaths, redeemScript, ripemd160, sha256, hash160, hash256, unknown)
                else -> Input.PartiallySignedInputWithoutUtxo(sighashType, derivationPaths, ripemd160, sha256, hash160, hash256, taprootKeySignature, taprootDerivationPaths, taprootInternalKey, unknown)
                // @formatter:on
            }
        }

        private fun createOutput(
            redeemScript: List?,
            witnessScript: List?,
            derivationPaths: Map,
            taprootInternalKey: XonlyPublicKey?,
            taprootDerivationPaths: Map,
            unknown: List
        ): Output = when {
            witnessScript != null -> Output.WitnessOutput(witnessScript, redeemScript, derivationPaths, taprootInternalKey, taprootDerivationPaths, unknown)
            redeemScript != null -> Output.NonWitnessOutput(redeemScript, derivationPaths, unknown)
            else -> Output.UnspecifiedOutput(derivationPaths, taprootInternalKey, taprootDerivationPaths, unknown)
        }

        private sealed class ReadEntryFailure {
            object DuplicateKeys : ReadEntryFailure()
            object InvalidData : ReadEntryFailure()
            object EndOfDataMap : ReadEntryFailure()
        }

        private fun readDataMap(input: fr.acinq.bitcoin.io.Input, entries: List = listOf()): Either> {
            return when (val result = readDataEntry(input)) {
                is Either.Right -> readDataMap(input, entries + result.value)
                is Either.Left -> when (result.value) {
                    is ReadEntryFailure.EndOfDataMap -> {
                        if (entries.map { it.key }.toSet().size != entries.size) {
                            Either.Left(ReadEntryFailure.DuplicateKeys)
                        } else {
                            Either.Right(entries)
                        }
                    }

                    is ReadEntryFailure.InvalidData -> Either.Left(ReadEntryFailure.InvalidData)
                    else -> Either.Left(result.value)
                }
            }
        }

        private fun readDataEntry(input: fr.acinq.bitcoin.io.Input): Either {
            if (input.availableBytes == 0) return Either.Left(ReadEntryFailure.InvalidData)
            val keyLength = BtcSerializer.varint(input).toInt()
            if (keyLength == 0) return Either.Left(ReadEntryFailure.EndOfDataMap)
            val key = input.readNBytes(keyLength) ?: return Either.Left(ReadEntryFailure.InvalidData)

            if (input.availableBytes == 0) return Either.Left(ReadEntryFailure.InvalidData)
            val valueLength = BtcSerializer.varint(input).toInt()
            val value = input.readNBytes(valueLength) ?: return Either.Left(ReadEntryFailure.InvalidData)

            return Either.Right(DataEntry(ByteVector(key), ByteVector(value)))
        }
    }

}

/**
 * @param prefix extended public key version bytes.
 * @param masterKeyFingerprint fingerprint of the master key.
 * @param extendedPublicKey BIP32 extended public key.
 */
public data class ExtendedPublicKeyWithMaster(@JvmField val prefix: Long, @JvmField val masterKeyFingerprint: Long, @JvmField val extendedPublicKey: DeterministicWallet.ExtendedPublicKey)

/**
 * @param masterKeyFingerprint fingerprint of the master key.
 * @param keyPath bip 32 derivation path.
 */
public data class KeyPathWithMaster(@JvmField val masterKeyFingerprint: Long, @JvmField val keyPath: KeyPath)

/**
 * @param masterKeyFingerprint fingerprint of the master key.
 * @param keyPath bip 32 derivation path.
 */
public data class TaprootBip32DerivationPath(@JvmField val leaves: List, @JvmField val masterKeyFingerprint: Long, @JvmField val keyPath: KeyPath) {
    public fun write(): ByteArray {
        val out = ByteArrayOutput()
        BtcSerializer.writeVarint(leaves.size, out)
        leaves.forEach { BtcSerializer.writeBytes(it, out) }
        BtcSerializer.writeBytes(ByteVector(Pack.writeInt32BE(masterKeyFingerprint.toInt())).concat(keyPath.path.map { ByteVector(Pack.writeInt32LE(it.toInt())) }), out)
        return out.toByteArray()
    }

    public companion object {
        public fun read(bin: ByteArray): TaprootBip32DerivationPath {
            val input = ByteArrayInput(bin)
            val numLeaves = BtcSerializer.varint(input).toInt()
            val leaves = (0 until numLeaves).map { BtcSerializer.bytes(input, 32).byteVector32() }
            val masterKeyFingerprint = Pack.int32BE(input).toLong()
            val childCount = (input.availableBytes / 4)
            val keyPath = KeyPath((0 until childCount).map { _ -> Pack.int32LE(input).toUInt().toLong() })
            return TaprootBip32DerivationPath(leaves, masterKeyFingerprint, keyPath)
        }
    }
}

public data class DataEntry(@JvmField val key: ByteVector, @JvmField val value: ByteVector)

/**
 * Global data for the PSBT.
 *
 * @param version psbt version.
 * @param tx partially signed transaction. NB: the transaction must be serialized with the "old" format (without witnesses).
 * @param extendedPublicKeys (optional) extended public keys used when signing inputs and producing outputs.
 * @param unknown (optional) unknown global entries.
 */
public data class Global(
    @JvmField val version: Long,
    @JvmField val tx: Transaction,
    @JvmField val extendedPublicKeys: List,
    @JvmField val unknown: List
)

/** A PSBT input. A valid PSBT must contain one such input per input of the [[Global.tx]]. */
public sealed class Input {
    // @formatter:off
    /** Non-witness utxo, used when spending non-segwit outputs (may also be included when spending segwit outputs). */
    public abstract val nonWitnessUtxo: Transaction?
    /** Witness utxo, used when spending segwit outputs. */
    public abstract val witnessUtxo: TxOut?
    /** Sighash type to be used when producing signatures for this output. */
    public abstract val sighashType: Int?
    /** Signatures as would be pushed to the stack from a scriptSig or witness. */
    public abstract val partialSigs: Map
    /** Derivation paths used for the signatures. */
    public abstract val derivationPaths: Map
    /** Redeem script for this input (when using p2sh). */
    public abstract val redeemScript: List?
    /** Witness script for this input (when using p2wsh). */
    public abstract val witnessScript: List?
    /** Fully constructed scriptSig with signatures and any other scripts necessary for the input to pass validation. */
    public abstract val scriptSig: List?
    /** Fully constructed scriptWitness with signatures and any other scripts necessary for the input to pass validation. */
    public abstract val scriptWitness: ScriptWitness?
    /** RipeMD160 preimages (e.g. for miniscript hash challenges). */
    public abstract val ripemd160: Set
    /** Sha256 preimages (e.g. for miniscript hash challenges). */
    public abstract val sha256: Set
    /** Hash160 preimages (e.g. for miniscript hash challenges). */
    public abstract val hash160: Set
    /** Hash256 preimages (e.g. for miniscript hash challenges). */
    public abstract val hash256: Set
    /** taproot keypath signature */
    public abstract val taprootKeySignature: ByteVector?
    /** Derivation paths used for taproot signatures, key is the internal key */
    public abstract val taprootDerivationPaths: Map
    /** Internal key used for taproot signatures */
    public abstract val taprootInternalKey: XonlyPublicKey?
    /** (optional) Unknown global entries. */
    public abstract val unknown: List
    // @formatter:on

    /**
     * A partially signed input without details about the utxo.
     * More signatures may need to be added before it can be finalized.
     */
    public data class PartiallySignedInputWithoutUtxo(
        override val sighashType: Int?,
        override val derivationPaths: Map,
        override val ripemd160: Set,
        override val sha256: Set,
        override val hash160: Set,
        override val hash256: Set,
        override val taprootKeySignature: ByteVector?,
        override val taprootDerivationPaths: Map,
        override val taprootInternalKey: XonlyPublicKey?,
        override val unknown: List,
    ) : Input() {
        override val nonWitnessUtxo: Transaction? = null
        override val witnessUtxo: TxOut? = null
        override val redeemScript: List? = null
        override val witnessScript: List? = null
        override val partialSigs: Map = mapOf()
        override val scriptSig: List? = null
        override val scriptWitness: ScriptWitness? = null
    }

    /**
     * A fully signed input without details about the utxo.
     * Input finalizers should keep the utxo to allow transaction extractors to verify the final network serialized
     * transaction, but it's not mandatory, so we may not have it available when parsing psbt inputs.
     */
    public data class FinalizedInputWithoutUtxo(
        override val scriptWitness: ScriptWitness?,
        override val scriptSig: List?,
        override val ripemd160: Set,
        override val sha256: Set,
        override val hash160: Set,
        override val hash256: Set,
        override val unknown: List
    ) : Input() {
        override val nonWitnessUtxo: Transaction? = null
        override val witnessUtxo: TxOut? = null
        override val sighashType: Int? = null
        override val partialSigs: Map = mapOf()
        override val derivationPaths: Map = mapOf()
        override val taprootKeySignature: ByteVector? = null
        override val taprootDerivationPaths: Map = mapOf()
        override val taprootInternalKey: XonlyPublicKey? = null
        override val redeemScript: List? = null
        override val witnessScript: List? = null
    }

    /** An input spending a segwit output. */
    public sealed class WitnessInput : Input() {
        public abstract val txOut: TxOut
        public val amount: Satoshi by lazy { txOut.amount }
        override val witnessUtxo: TxOut? by lazy { txOut }

        /** A partially signed segwit input. More signatures may need to be added before it can be finalized. */
        public data class PartiallySignedWitnessInput(
            override val txOut: TxOut,
            override val nonWitnessUtxo: Transaction?,
            override val sighashType: Int?,
            override val partialSigs: Map,
            override val derivationPaths: Map,
            override val redeemScript: List?,
            override val witnessScript: List?,
            override val ripemd160: Set,
            override val sha256: Set,
            override val hash160: Set,
            override val hash256: Set,
            override val taprootKeySignature: ByteVector?,
            override val taprootDerivationPaths: Map,
            override val taprootInternalKey: XonlyPublicKey?,
            override val unknown: List
        ) : WitnessInput() {
            override val scriptSig: List? = null
            override val scriptWitness: ScriptWitness? = null
        }

        /** A fully signed segwit input. */
        public data class FinalizedWitnessInput(
            override val txOut: TxOut,
            override val nonWitnessUtxo: Transaction?,
            override val scriptWitness: ScriptWitness,
            override val scriptSig: List?,
            override val ripemd160: Set,
            override val sha256: Set,
            override val hash160: Set,
            override val hash256: Set,
            override val unknown: List
        ) : WitnessInput() {
            override val sighashType: Int? = null
            override val partialSigs: Map = mapOf()
            override val derivationPaths: Map = mapOf()
            override val taprootKeySignature: ByteVector? = null
            override val taprootDerivationPaths: Map = mapOf()
            override val taprootInternalKey: XonlyPublicKey? = null
            override val redeemScript: List? = null
            override val witnessScript: List? = null
        }
    }

    /** An input spending a non-segwit output. */
    public sealed class NonWitnessInput : Input() {
        public abstract val inputTx: Transaction
        public abstract val outputIndex: Int
        public val amount: Satoshi by lazy { inputTx.txOut[outputIndex].amount }
        override val nonWitnessUtxo: Transaction? by lazy { inputTx }

        // The following fields should only be present for inputs which spend segwit outputs (including P2SH embedded ones).
        override val witnessUtxo: TxOut? = null
        override val witnessScript: List? = null
        override val taprootKeySignature: ByteVector? = null
        override val taprootDerivationPaths: Map = mapOf()
        override val taprootInternalKey: XonlyPublicKey? = null

        /** A partially signed non-segwit input. More signatures may need to be added before it can be finalized. */
        public data class PartiallySignedNonWitnessInput(
            override val inputTx: Transaction,
            override val outputIndex: Int,
            override val sighashType: Int?,
            override val partialSigs: Map,
            override val derivationPaths: Map,
            override val redeemScript: List?,
            override val ripemd160: Set,
            override val sha256: Set,
            override val hash160: Set,
            override val hash256: Set,
            override val unknown: List
        ) : NonWitnessInput() {
            override val scriptSig: List? = null
            override val scriptWitness: ScriptWitness? = null
        }

        /** A fully signed non-segwit input. */
        public data class FinalizedNonWitnessInput(
            override val inputTx: Transaction,
            override val outputIndex: Int,
            override val scriptSig: List,
            override val ripemd160: Set,
            override val sha256: Set,
            override val hash160: Set,
            override val hash256: Set,
            override val unknown: List
        ) : NonWitnessInput() {
            override val sighashType: Int? = null
            override val partialSigs: Map = mapOf()
            override val derivationPaths: Map = mapOf()
            override val redeemScript: List? = null
            override val witnessScript: List? = null
            override val scriptWitness: ScriptWitness? = null
        }
    }
}

/** A PSBT output. A valid PSBT must contain one such output per output of the [[Global.tx]]. */
public sealed class Output {
    // @formatter:off
    /** Redeem script for this output (when using p2sh). */
    public abstract val redeemScript: List?
    /** Witness script for this output (when using p2wsh). */
    public abstract val witnessScript: List?
    /** Derivation paths used to produce the public keys associated to this output. */
    public abstract val derivationPaths: Map
    /** Internal key used to produce the public key associated to this output. */
    public abstract val taprootInternalKey: XonlyPublicKey?
    /** Taproot Derivation paths used to produce the public keys associated to this output. */
    public abstract val taprootDerivationPaths: Map
    /** (optional) Unknown global entries. */
    public abstract val unknown: List
    // @formatter:on

    /** A non-segwit output. */
    public data class NonWitnessOutput(
        override val redeemScript: List?,
        override val derivationPaths: Map,
        override val unknown: List
    ) : Output() {
        override val witnessScript: List? = null
        override val taprootInternalKey: XonlyPublicKey? = null
        override val taprootDerivationPaths: Map = mapOf()
    }

    /** A segwit output. */
    public data class WitnessOutput(
        override val witnessScript: List?,
        override val redeemScript: List?,
        override val derivationPaths: Map,
        override val taprootInternalKey: XonlyPublicKey?,
        override val taprootDerivationPaths: Map,
        override val unknown: List
    ) : Output()

    /** An output for which usage of segwit is currently unknown. */
    public data class UnspecifiedOutput(
        override val derivationPaths: Map,
        override val taprootInternalKey: XonlyPublicKey?,
        override val taprootDerivationPaths: Map,
        override val unknown: List
    ) : Output() {
        override val redeemScript: List? = null
        override val witnessScript: List? = null
    }
}

public sealed class UpdateFailure {
    public data class InvalidInput(val reason: String) : UpdateFailure()
    public data class InvalidNonWitnessUtxo(val reason: String) : UpdateFailure()
    public data class InvalidWitnessUtxo(val reason: String) : UpdateFailure()
    public data class CannotCombine(val reason: String) : UpdateFailure()
    public data class CannotJoin(val reason: String) : UpdateFailure()
    public data class CannotUpdateInput(val index: Int, val reason: String) : UpdateFailure()
    public data class CannotUpdateOutput(val index: Int, val reason: String) : UpdateFailure()
    public data class CannotSignInput(val index: Int, val reason: String) : UpdateFailure()
    public data class CannotFinalizeInput(val index: Int, val reason: String) : UpdateFailure()
    public data class CannotExtractTx(val reason: String) : UpdateFailure()
}

public data class SignPsbtResult(val psbt: Psbt, val sig: ByteVector)

public sealed class ParseFailure {
    public object InvalidMagicBytes : ParseFailure()
    public object InvalidSeparator : ParseFailure()
    public object DuplicateKeys : ParseFailure()
    public data class InvalidPsbtVersion(val reason: String) : ParseFailure()
    public data class UnsupportedPsbtVersion(val version: Long) : ParseFailure()
    public data class InvalidGlobalTx(val reason: String) : ParseFailure()
    public object GlobalTxMissing : ParseFailure()
    public data class InvalidExtendedPublicKey(val reason: String) : ParseFailure()
    public data class InvalidTxInput(val reason: String) : ParseFailure()
    public data class InvalidTxOutput(val reason: String) : ParseFailure()
    public object InvalidContent : ParseFailure()
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy