// Copyright (c) 2018 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package psbt // The Finalizer requires provision of a single PSBT input // in which all necessary signatures are encoded, and // uses it to construct valid final scriptSig and scriptWitness // fields. // NOTE that p2sh (legacy) and p2wsh currently support only // multisig and no other custom script. import ( "bytes" "errors" "io" "sort" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" ) // A utility function due to non-exported witness serialization // (writeTxWitness encodes the bitcoin protocol encoding for a transaction // input's witness into w). func writeTxWitness(w io.Writer, wit [][]byte) error { err := wire.WriteVarInt(w, 0, uint64(len(wit))) if err != nil { return err } for _, item := range wit { err = wire.WriteVarBytes(w, 0, item) if err != nil { return err } } return nil } // writePKHWitness writes a witness for a p2wkh spending input func writePKHWitness(sig []byte, pub []byte) ([]byte, error) { var buf bytes.Buffer var witnessItems = [][]byte{sig, pub} err := writeTxWitness(&buf, witnessItems) if err != nil { return nil, err } return buf.Bytes(), nil } // checkIsMultisigScript is a utility function to check wheter // a given redeemscript fits the standard multisig template used // in all p2sh based multisig, given a set of pubkeys for redemption. func checkIsMultiSigScript(pubKeys [][]byte, sigs [][]byte, script []byte) bool { // First insist that the script type is multisig if txscript.GetScriptClass(script) != txscript.MultiSigTy { return false } // Inspect the script to ensure that the number of sigs and // pubkeys is correct numSigs, numPubKeys, err := txscript.CalcMultiSigStats(script) if err != nil { return false } if numPubKeys != len(pubKeys) || numSigs != len(sigs) { return false } return true } // extractKeyOrderFromScript is a utility function // to extract an ordered list of signatures, given // a serialized script (redeemscript or witness script), // a list of pubkeys and the signatures corresponding to those // pubkeys, so that the signatures will be embedded in the final // scriptSig or scriptWitness in the correct order. func extractKeyOrderFromScript(script []byte, expectedPubkeys [][]byte, sigs [][]byte) ([][]byte, error) { if !checkIsMultiSigScript(expectedPubkeys, sigs, script) { return nil, ErrUnsupportedScriptType } // Arrange the pubkeys and sigs into a slice of format: // [[pub,sig], [pub,sig],..] pubsSigs := [][][]byte{} for i, pub := range expectedPubkeys { tmp := [][]byte{pub, sigs[i]} pubsSigs = append(pubsSigs, tmp) } type kv struct { Key int Value [][]byte } var positionMap []kv for _, p := range pubsSigs { pos := bytes.Index(script, p[0]) if pos < 0 { return nil, errors.New("Script does not contain pubkeys") } positionMap = append(positionMap, kv{Key: pos, Value: p}) } sort.Slice(positionMap, func(i, j int) bool { return positionMap[i].Key < positionMap[j].Key }) // Build the return array of signatures sigsNew := [][]byte{} for _, x := range positionMap { sigsNew = append(sigsNew, x.Value[1]) } return sigsNew, nil } // getMultisigScriptWitness creates a full Witness field for the transaction, // given the public keys and signatures to be appended, after checking // that the witnessScript is of type M of N multisig. This // is used for both p2wsh and nested p2wsh multisig cases. func getMultisigScriptWitness(witnessScript []byte, pubKeys [][]byte, sigs [][]byte) ([]byte, error) { orderedSigs, err := extractKeyOrderFromScript(witnessScript, pubKeys, sigs) if err != nil { return nil, err } var buf bytes.Buffer var witnessItems = [][]byte{ nil} for _, os := range orderedSigs { witnessItems = append(witnessItems, os) } witnessItems = append(witnessItems, witnessScript) err = writeTxWitness(&buf, witnessItems) if err != nil { return nil, err } return buf.Bytes(), nil } // checkSigHashFlags compares the sighash flag byte on a signature with the // value expected according to any PsbtInSighashType field in this section // of the PSBT, and returns true if they match, false otherwise. // If no SighashType field exists, it is assumed to be SIGHASH_ALL. // TODO sighash type not restricted to one byte in future? func checkSigHashFlags(sig []byte, input *PInput) bool { expectedSighashType := txscript.SigHashAll if input.SighashType != 0 { expectedSighashType = input.SighashType } return expectedSighashType == txscript.SigHashType(sig[len(sig)-1]) } // isFinalized considers this input finalized if it contains // at least one of the FinalScriptSig or FinalScriptWitness // are filled (which only occurs in a successful call to Finalize*) func isFinalized(p *Psbt, inIndex int) bool { input := p.Inputs[inIndex] return input.FinalScriptSig != nil || input.FinalScriptWitness != nil } // isFinalizable checks whether the structure of the entry // for the input of the Psbt p at index inIndex contains sufficient // information to finalize this input. Deduce the template // from the contents. func isFinalizable(p *Psbt, inIndex int) bool { pInput := p.Inputs[inIndex] // Cannot be finalizable without any signatures if pInput.PartialSigs == nil { return false } if pInput.WitnessUtxo != nil { if txscript.IsWitnessProgram(pInput.WitnessUtxo.PkScript) { if txscript.IsPayToWitnessScriptHash(pInput.WitnessUtxo.PkScript) { if pInput.WitnessScript == nil || pInput.RedeemScript != nil { return false } } else { // if it's p2wkh there should be no redeemScript or witnessScript if pInput.WitnessScript != nil || pInput.RedeemScript != nil { return false } } } else if txscript.IsPayToScriptHash(pInput.WitnessUtxo.PkScript) { if pInput.RedeemScript == nil { return false } // if it's nested, and it's p2wsh, it must have WitnessScript; // if p2wkh, it must not. if txscript.IsPayToWitnessScriptHash(pInput.RedeemScript) { if pInput.WitnessScript == nil { return false } } else if txscript.IsPayToWitnessPubKeyHash(pInput.RedeemScript) { if pInput.WitnessScript != nil { return false } } else { // unrecognized type return false } } } else if pInput.NonWitnessUtxo != nil { if pInput.WitnessScript != nil { return false } outIndex := p.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index if txscript.IsPayToScriptHash(pInput.NonWitnessUtxo.TxOut[outIndex].PkScript) { if pInput.RedeemScript == nil { return false } } else { if pInput.RedeemScript != nil { return false } } } else { // one of witness and nonwitness utxo must be present return false } return true } // MaybeFinalize attempts to finalize the input at index inIndex // in the PSBT p, returning true with no error if it succeeds, OR // if the input has already been finalized. func MaybeFinalize(p *Psbt, inIndex int) (bool, error) { if isFinalized(p, inIndex) { return true, nil } if !isFinalizable(p, inIndex) { return false, ErrNotFinalizable } err := Finalize(p, inIndex) if err != nil { return false, err } return true, nil } // MaybeFinalizeAll attempts to finalize all inputs of the Psbt that // are not already finalized, and returns an error if it fails to do so. func MaybeFinalizeAll(p *Psbt) error { for i := range p.UnsignedTx.TxIn { success, err := MaybeFinalize(p, i) if err != nil || !success { return err } } return nil } // Finalize assumes that the provided Psbt struct // has all partial signatures and redeem scripts/witness scripts // already prepared for the specified input, and so removes all temporary // data and replaces them with completed scriptSig and witness // fields, which are stored in key-types 07 and 08. The witness/ // non-witness utxo fields in the inputs (key-types 00 and 01) are // left intact as they may be needed for validation (?). // If there is any invalid or incomplete data, an error is // returned. func Finalize(p *Psbt, inIndex int) error { var err error pInput := p.Inputs[inIndex] if pInput.WitnessUtxo != nil { err = FinalizeWitness(p, inIndex) if err != nil { return err } } else if pInput.NonWitnessUtxo != nil { err = FinalizeNonWitness(p, inIndex) if err != nil { return err } } else { return ErrInvalidPsbtFormat } if err = p.SanityCheck(); err != nil { return err } return nil } // checkFinalScriptSigWitness checks whether a given input in the // Psbt struct already has the fields 07 (FinalInScriptSig) or 08 // (FinalInWitness). If so, it returns true. It does not modify the // Psbt. func checkFinalScriptSigWitness(p *Psbt, inIndex int) bool { pInput := p.Inputs[inIndex] if pInput.FinalScriptSig != nil { return true } if pInput.FinalScriptWitness != nil { return true } return false } // FinalizeNonWitness attempts to create PsbtInFinalScriptSig field // for input at index inIndex, and removes all other fields except // for the utxo field, for an input of type non-witness, or returns // an error. func FinalizeNonWitness(p *Psbt, inIndex int) error { if checkFinalScriptSigWitness(p, inIndex) { return ErrInputAlreadyFinalized } // Construct a scriptSig given the pubkey, signature (keytype 02), // of which there might be multiple, and the redeem script // field (keytype 04) if present (note, it is not present // for p2pkh type inputs). var scriptSig []byte var err error pInput := p.Inputs[inIndex] containsRedeemScript := pInput.RedeemScript != nil var pubKeys [][]byte var sigs [][]byte for _, ps := range pInput.PartialSigs { pubKeys = append(pubKeys, ps.PubKey) sigOK := checkSigHashFlags(ps.Signature, &pInput) if !sigOK { return ErrInvalidSigHashFlags } sigs = append(sigs, ps.Signature) } if len(sigs) < 1 || len(pubKeys) < 1 { // We have failed to identify at least 1 (sig, pub) pair // in the PSBT, which indicates it was not ready to be finalized. return ErrNotFinalizable } if !containsRedeemScript { // p2pkh - insist on one sig/pub and build scriptSig if len(sigs) != 1 || len(pubKeys) != 1 { return ErrNotFinalizable } builder := txscript.NewScriptBuilder() builder.AddData(sigs[0]).AddData(pubKeys[0]) scriptSig, err = builder.Script() if err != nil { return err } } else { // This is assumed p2sh multisig // Given redeemScript and pubKeys we can decide in what order // signatures must be appended. orderedSigs, err := extractKeyOrderFromScript(pInput.RedeemScript, pubKeys, sigs) if err != nil { return err } // TODO the below is specific to the multisig case. builder := txscript.NewScriptBuilder() builder.AddOp(txscript.OP_FALSE) for _, os := range orderedSigs { builder.AddData(os) } builder.AddData(pInput.RedeemScript) scriptSig, err = builder.Script() if err != nil { return err } } // At this point, a scriptSig has been constructed. // Remove all fields other than non-witness utxo (00) // and finaliscriptsig (07) newInput := NewPsbtInput(pInput.NonWitnessUtxo, nil) newInput.FinalScriptSig = scriptSig // overwrite the entry in the input list at the correct index // Note that this removes all the other entries in the list for // this input index. p.Inputs[inIndex] = *newInput return nil } // FinalizeWitness attempts to create PsbtInFinalScriptSig field // and PsbtInFinalScriptWitness field for input at index inIndex, // and removes all other fields except for the utxo field, for an // input of type witness, or returns an error. func FinalizeWitness(p *Psbt, inIndex int) error { if checkFinalScriptSigWitness(p, inIndex) { return ErrInputAlreadyFinalized } // Construct a scriptSig given the redeem script // field (keytype 04) if present (if not present it's empty // as per bip141). // Fill this in in field FinalScriptSig (keytype 07). // And/or construct a FinalScriptWitness field (keytype 08), // assuming either p2wkh or p2wsh multisig. var scriptSig []byte var witness []byte var err error pInput := p.Inputs[inIndex] containsRedeemScript := pInput.RedeemScript != nil cointainsWitnessScript := pInput.WitnessScript != nil var pubKeys [][]byte var sigs [][]byte for _, ps := range pInput.PartialSigs { pubKeys = append(pubKeys, ps.PubKey) sigOK := checkSigHashFlags(ps.Signature, &pInput) if !sigOK { return ErrInvalidSigHashFlags } sigs = append(sigs, ps.Signature) } if len(sigs) == 0 || len(pubKeys) == 0 { return ErrNotFinalizable } if !containsRedeemScript { if len(pubKeys) == 1 && len(sigs) == 1 && !cointainsWitnessScript { // p2wkh case witness, err = writePKHWitness(sigs[0], pubKeys[0]) if err != nil { return err } } else { // Otherwise, we must have a witnessScript field, // to fulfil the requirements of p2wsh // NOTE (we tacitly assume multisig) if !cointainsWitnessScript { return ErrNotFinalizable } witness, err = getMultisigScriptWitness(pInput.WitnessScript, pubKeys, sigs) if err != nil { return err } } } else { // This is currently assumed p2wsh, multisig, nested in p2sh, // or p2wkh, nested in p2sh. // The scriptSig is taken from the redeemscript field, but embedded // in a push builder := txscript.NewScriptBuilder() builder.AddData(pInput.RedeemScript) scriptSig, err = builder.Script() if err != nil { return err } if !cointainsWitnessScript { // Assumed p2sh-p2wkh // Here the witness is just (sig, pub) as for p2pkh case if len(sigs) != 1 || len(pubKeys) != 1 { return ErrNotFinalizable } witness, err = writePKHWitness(sigs[0], pubKeys[0]) if err != nil { return err } } else { // Assumed p2sh-p2wsh with multisig. // To build the witness, we do exactly as for the native p2wsh case. witness, err = getMultisigScriptWitness(pInput.WitnessScript, pubKeys, sigs) if err != nil { return err } } } // At this point, a witness has been constructed, // and a scriptSig (if nested; else it's []). // Remove all fields other than witness utxo (01) // and finalscriptsig (07), finalscriptwitness (08) newInput := NewPsbtInput(nil, pInput.WitnessUtxo) if len(scriptSig) > 0 { newInput.FinalScriptSig = scriptSig } newInput.FinalScriptWitness = witness // overwrite the entry in the input list at the correct index p.Inputs[inIndex] = *newInput return nil }