diff --git a/psbt/finalizer.go b/psbt/finalizer.go index 5fa3f9e..0bf9dbb 100644 --- a/psbt/finalizer.go +++ b/psbt/finalizer.go @@ -6,480 +6,457 @@ 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 +// uses it to construct valid final sigScript 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 { +// 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 *Packet, 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] +// isFinalizableWitnessInput returns true if the target input is a witness UTXO +// that can be finalized. +func isFinalizableWitnessInput(pInput *PInput) bool { + pkScript := pInput.WitnessUtxo.PkScript - // 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 { + switch { + // If this is a native witness output, then we require both + // the witness script, but not a redeem script. + case txscript.IsWitnessProgram(pkScript): + if txscript.IsPayToWitnessScriptHash(pkScript) { + if pInput.WitnessScript == nil || + pInput.RedeemScript != nil { return false } } else { - if pInput.RedeemScript != nil { + // A P2WKH output on the other hand doesn't need + // neither a witnessScript or redeemScript. + if pInput.WitnessScript != nil || + pInput.RedeemScript != nil { return false } } - } else { - // one of witness and nonwitness utxo must be present + + // For nested P2SH inputs, we verify that a witness script is known. + case txscript.IsPayToScriptHash(pkScript): + if pInput.RedeemScript == nil { + return false + } + + // If this is a nested P2SH input, then it must also have a + // witness script, while we don't need one for P2WKH. + 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 + } + + // If this isn't a nested nested P2SH output or a native witness + // output, then we can't finalize this input as we don't understand it. + default: 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) { +// isFinalizableLegacyInput returns true of the passed input a legacy input +// (non-witness) that can be finalized. +func isFinalizableLegacyInput(p *Packet, pInput *PInput, inIndex int) bool { + // If the input has a witness, then it's invalid. + if pInput.WitnessScript != nil { + return false + } + + // Otherwise, we'll verify that we only have a RedeemScript if the prev + // output script is P2SH. + 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 + } + } + + return true +} + +// isFinalizable checks whether the structure of the entry for the input of the +// psbt.Packet at index inIndex contains sufficient information to finalize +// this input. +func isFinalizable(p *Packet, inIndex int) bool { + pInput := p.Inputs[inIndex] + + // The input cannot be finalized without any signatures + if pInput.PartialSigs == nil { + return false + } + + // For an input to be finalized, we'll one of two possible top-level + // UTXOs present. Each UTXO type has a distinct set of requirements to + // be considered finalized. + switch { + + // A witness input must be either native P2WSH or nested P2SH with all + // relevant sigScript or witness data populated. + case pInput.WitnessUtxo != nil: + if !isFinalizableWitnessInput(&pInput) { + return false + } + + case pInput.NonWitnessUtxo != nil: + if !isFinalizableLegacyInput(p, &pInput, inIndex) { + return false + } + + // If neither a known UTXO type isn't present at all, then we'll + // return false as we need one of them. + default: + 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 *Packet, 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 { + + if err := Finalize(p, inIndex); 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 { +// MaybeFinalizeAll attempts to finalize all inputs of the psbt.Packet that are +// not already finalized, and returns an error if it fails to do so. +func MaybeFinalizeAll(p *Packet) 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 +// Finalize assumes that the provided psbt.Packet 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 sigScript 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 *Packet, inIndex int) error { pInput := p.Inputs[inIndex] - if pInput.WitnessUtxo != nil { - err = FinalizeWitness(p, inIndex) - if err != nil { + + // Depending on the UTXO type, we either attempt to finalize it as a + // witness or legacy UTXO. + switch { + case pInput.WitnessUtxo != nil: + if err := finalizeWitnessInput(p, inIndex); err != nil { return err } - } else if pInput.NonWitnessUtxo != nil { - err = FinalizeNonWitness(p, inIndex) - if err != nil { + + case pInput.NonWitnessUtxo != nil: + if err := finalizeNonWitnessInput(p, inIndex); err != nil { return err } - } else { + + default: return ErrInvalidPsbtFormat } - if err = p.SanityCheck(); err != nil { + // Before returning we sanity check the PSBT to ensure we don't extract + // an invalid transaction or produce an invalid intermediate state. + 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 { +// checkFinalScriptSigWitness checks whether a given input in the psbt.Packet +// struct already has the fields 07 (FinalInScriptSig) or 08 (FinalInWitness). +// If so, it returns true. It does not modify the Psbt. +func checkFinalScriptSigWitness(p *Packet, 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 { +// finalizeNonWitnessInput attempts to create a PsbtInFinalScriptSig field for +// the 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 finalizeNonWitnessInput(p *Packet, inIndex int) error { + // If this input has already been finalized, then we'll return an error + // as we can't proceed. 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 + + // Our goal here is to construct a sigScript 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 + var sigScript []byte + pInput := p.Inputs[inIndex] containsRedeemScript := pInput.RedeemScript != nil - var pubKeys [][]byte - var sigs [][]byte + + var ( + pubKeys [][]byte + 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) } + // We have failed to identify at least 1 (sig, pub) pair in the PSBT, + // which indicates it was not ready to be finalized. As a result, we + // can't proceed. 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 this input doesn't need a redeem script (P2PKH), then we'll + // construct a simple sigScript that's just the signature then the + // pubkey (OP_CHECKSIG). + var err error if !containsRedeemScript { - // p2pkh - insist on one sig/pub and build scriptSig + // At this point, we should only have a single signature and + // pubkey. if len(sigs) != 1 || len(pubKeys) != 1 { return ErrNotFinalizable } + + // In this case, our sigScript is just: . builder := txscript.NewScriptBuilder() builder.AddData(sigs[0]).AddData(pubKeys[0]) - scriptSig, err = builder.Script() + sigScript, 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) + // 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. + + // At this point, we assume that this is a mult-sig input, so + // we construct our sigScript which looks something like this + // (mind the extra element for the extra multi-sig pop): + // * + // + // TODO(waxwing): 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() + sigScript, 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) + + // At this point, a sigScript 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. + newInput.FinalScriptSig = sigScript + + // 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 { +// finalizeWitnessInput 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 finalizeWitnessInput(p *Packet, inIndex int) error { + // If this input has already been finalized, then we'll return an error + // as we can't proceed. 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 + + // Depending on the actual output type, we'll either populate a + // serializedWitness or a witness as well asa sigScript. + var ( + sigScript []byte + serializedWitness []byte + ) + pInput := p.Inputs[inIndex] - containsRedeemScript := pInput.RedeemScript != nil - cointainsWitnessScript := pInput.WitnessScript != nil - var pubKeys [][]byte - var sigs [][]byte + + // First we'll validate and collect the pubkey+sig pairs from the set + // of partial signatures. + var ( + pubKeys [][]byte + 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 at this point, we don't have any pubkey+sig pairs, then we bail + // as we can't proceed. if len(sigs) == 0 || len(pubKeys) == 0 { return ErrNotFinalizable } + + containsRedeemScript := pInput.RedeemScript != nil + cointainsWitnessScript := pInput.WitnessScript != nil + + // If there's no redeem script, then we assume that this is native + // segwit input. + var err error if !containsRedeemScript { - if len(pubKeys) == 1 && len(sigs) == 1 && !cointainsWitnessScript { - // p2wkh case - witness, err = writePKHWitness(sigs[0], pubKeys[0]) + // If we have only a sigley pubkey+sig pair, and no witness + // script, then we assume this is a P2WKH input. + if len(pubKeys) == 1 && len(sigs) == 1 && + !cointainsWitnessScript { + + serializedWitness, 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) + // Otherwise, we must have a witnessScript field, so + // we'll generate a valid multi-sig witness. + // + // NOTE: We tacitly assume multisig. + // + // TODO(roasbeef): need to add custom finalize for + // non-multisig P2WSH outputs (HTLCs, delay outputs, + // etc). if !cointainsWitnessScript { return ErrNotFinalizable } - witness, err = getMultisigScriptWitness(pInput.WitnessScript, - pubKeys, sigs) + + serializedWitness, 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 + // Otherwise, we assume that this is a p2wsh multi-sig output, + // which is nested in a p2sh, or a p2wkh nested in a p2sh. + // + // In this case, we'll take the redeem script (the witness + // program in this case), and push it on the stack within the + // sigScript. builder := txscript.NewScriptBuilder() builder.AddData(pInput.RedeemScript) - scriptSig, err = builder.Script() + sigScript, err = builder.Script() if err != nil { return err } + + // If don't have a witness script, then we assume this is a + // nested p2wkh output. if !cointainsWitnessScript { - // Assumed p2sh-p2wkh - // Here the witness is just (sig, pub) as for p2pkh case + // 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]) + + serializedWitness, 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) + // Otherwise, we assume that this is a p2wsh multi-sig, + // so we generate the proper witness. + serializedWitness, 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) + + // At this point, a witness has been constructed, and a sigScript (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 + if len(sigScript) > 0 { + newInput.FinalScriptSig = sigScript } - newInput.FinalScriptWitness = witness - // overwrite the entry in the input list at the correct index + + newInput.FinalScriptWitness = serializedWitness + + // Finally, we overwrite the entry in the input list at the correct + // index. p.Inputs[inIndex] = *newInput return nil } diff --git a/psbt/partialsig.go b/psbt/partialsig.go new file mode 100644 index 0000000..7211d25 --- /dev/null +++ b/psbt/partialsig.go @@ -0,0 +1,58 @@ +package psbt + +import ( + "bytes" + + "github.com/btcsuite/btcd/btcec" +) + +// PartialSig encapsulate a (BTC public key, ECDSA signature) +// pair, note that the fields are stored as byte slices, not +// btcec.PublicKey or btcec.Signature (because manipulations will +// be with the former not the latter, here); compliance with consensus +// serialization is enforced with .checkValid() +type PartialSig struct { + PubKey []byte + Signature []byte +} + +// PartialSigSorter implements sort.Interface for PartialSig. +type PartialSigSorter []*PartialSig + +func (s PartialSigSorter) Len() int { return len(s) } + +func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s PartialSigSorter) Less(i, j int) bool { + return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 +} + +// validatePubkey checks if pubKey is *any* valid pubKey serialization in a +// Bitcoin context (compressed/uncomp. OK). +func validatePubkey(pubKey []byte) bool { + _, err := btcec.ParsePubKey(pubKey, btcec.S256()) + if err != nil { + return false + } + return true +} + +// validateSignature checks that the passed byte slice is a valid DER-encoded +// ECDSA signature, including the sighash flag. It does *not* of course +// validate the signature against any message or public key. +func validateSignature(sig []byte) bool { + _, err := btcec.ParseDERSignature(sig, btcec.S256()) + if err != nil { + return false + } + return true +} + +// checkValid checks that both the pbukey and sig are valid. See the methods +// (PartialSig, validatePubkey, validateSignature) for more details. +// +// TODO(waxwing): update for Schnorr will be needed here if/when that +// activates. +func (ps *PartialSig) checkValid() bool { + return validatePubkey(ps.PubKey) && validateSignature(ps.Signature) +} diff --git a/psbt/utils.go b/psbt/utils.go new file mode 100644 index 0000000..5af0b14 --- /dev/null +++ b/psbt/utils.go @@ -0,0 +1,272 @@ +// 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 + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "sort" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +// writeTxWitness is a 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 { + if err := wire.WriteVarInt(w, 0, uint64(len(wit))); 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 + witnessItems = [][]byte{sig, pub} + ) + + if err := writeTxWitness(&buf, witnessItems); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// checkIsMultisigScript is a utility function to check whether 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 the number of sigs provided, doesn't match the number of required + // pubkeys, then we can't proceed as we're not yet final. + 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. This +// function is used to ensure 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 this isn't a proper finalized multi-sig script, then we can't + // proceed. + if !checkIsMultiSigScript(expectedPubkeys, sigs, script) { + return nil, ErrUnsupportedScriptType + } + + // Arrange the pubkeys and sigs into a slice of format: + // * [[pub,sig], [pub,sig],..] + type sigWithPub struct { + pubKey []byte + sig []byte + } + var pubsSigs []sigWithPub + for i, pub := range expectedPubkeys { + pubsSigs = append(pubsSigs, sigWithPub{ + pubKey: pub, + sig: sigs[i], + }) + } + + // Now that we have the set of (pubkey, sig) pairs, we'll construct a + // position map that we can use to swap the order in the slice above to + // match how things are laid out in the script. + type positionEntry struct { + index int + value sigWithPub + } + var positionMap []positionEntry + + // For each pubkey in our pubsSigs slice, we'll now construct a proper + // positionMap entry, based on _where_ in the script the pubkey first + // appears. + for _, p := range pubsSigs { + pos := bytes.Index(script, p.pubKey) + if pos < 0 { + return nil, errors.New("script does not contain pubkeys") + } + + positionMap = append(positionMap, positionEntry{ + index: pos, + value: p, + }) + } + + // Now that we have the position map full populated, we'll use the + // index data to properly sort the entries in the map based on where + // they appear in the script. + sort.Slice(positionMap, func(i, j int) bool { + return positionMap[i].index < positionMap[j].index + }) + + // Finally, we can simply iterate through the position map in order to + // extract the proper signature ordering. + sortedSigs := make([][]byte, 0, len(positionMap)) + for _, x := range positionMap { + sortedSigs = append(sortedSigs, x.value.sig) + } + + return sortedSigs, nil +} + +// getMultisigScriptWitness creates a full psbt serialized Witness field for +// the transaction, given the public keys and signatures to be appended. This +// function will only accept witnessScripts of the 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) { + + // First using the script as a guide, we'll properly order the sigs + // according to how their corresponding pubkeys appear in the + // witnessScript. + orderedSigs, err := extractKeyOrderFromScript( + witnessScript, pubKeys, sigs, + ) + if err != nil { + return nil, err + } + + // Now that we know the proper order, we'll append each of the + // signatures into a new witness stack, then top it off with the + // witness script at the end, prepending the nil as we need the extra + // pop.. + witnessElements := make(wire.TxWitness, 0, len(sigs)+2) + witnessElements = append(witnessElements, nil) + for _, os := range orderedSigs { + witnessElements = append(witnessElements, os) + } + witnessElements = append(witnessElements, witnessScript) + + // Now that we have the full witness stack, we'll serialize it in the + // expected format, and return the final bytes. + var buf bytes.Buffer + if err = writeTxWitness(&buf, witnessElements); 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(waxwing): 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]) +} + +// serializeKVpair writes out a kv pair using a varbyte prefix for each. +func serializeKVpair(w io.Writer, key []byte, value []byte) error { + if err := wire.WriteVarBytes(w, 0, key); err != nil { + return err + } + + return wire.WriteVarBytes(w, 0, value) +} + +// serializeKVPairWithType writes out to the passed writer a type coupled with +// a key. +func serializeKVPairWithType(w io.Writer, kt uint8, keydata []byte, + value []byte) error { + + // If the key has no data, then we write a blank slice. + if keydata == nil { + keydata = []byte{} + } + + // The final key to be written is: {type} || {keyData} + serializedKey := append([]byte{byte(kt)}, keydata...) + return serializeKVpair(w, serializedKey, value) +} + +// getKey retrieves a single key - both the key type and the keydata (if +// present) from the stream and returns the key type as an integer, or -1 if +// the key was of zero length. This integer is is used to indicate the presence +// of a separator byte which indicates the end of a given key-value pair list, +// and the keydata as a byte slice or nil if none is present. +func getKey(r io.Reader) (int, []byte, error) { + + // For the key, we read the varint separately, instead of using the + // available ReadVarBytes, because we have a specific treatment of 0x00 + // here: + count, err := wire.ReadVarInt(r, 0) + if err != nil { + return -1, nil, ErrInvalidPsbtFormat + } + if count == 0 { + // A separator indicates end of key-value pair list. + return -1, nil, nil + } + + // Next, we ready out the designated number of bytes, which may include + // a type, key, and optional data. + keyTypeAndData := make([]byte, count) + if _, err := io.ReadFull(r, keyTypeAndData[:]); err != nil { + return -1, nil, err + } + + keyType := int(string(keyTypeAndData)[0]) + + // Note that the second return value will usually be empty, since most + // keys contain no more than the key type byte. + if len(keyTypeAndData) == 1 { + return keyType, nil, nil + } + + // Otherwise, we return the key, along with any data that it may + // contain. + return keyType, keyTypeAndData[1:], nil + +} + +// readTxOut is a limited version of wire.ReadTxOut, because the latter is not +// exported. +func readTxOut(txout []byte) (*wire.TxOut, error) { + if len(txout) < 10 { + return nil, ErrInvalidPsbtFormat + } + + valueSer := binary.LittleEndian.Uint64(txout[:8]) + scriptPubKey := txout[9:] + + return wire.NewTxOut(int64(valueSer), scriptPubKey), nil +}