Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem about making modular transaction with psbt #2004

Closed
Eoous opened this issue Jul 13, 2023 · 9 comments
Closed

Problem about making modular transaction with psbt #2004

Eoous opened this issue Jul 13, 2023 · 9 comments

Comments

@Eoous
Copy link
Contributor

Eoous commented Jul 13, 2023

I want to create 2 of 2 multisig tx with psbt. But got error: non-mandatory-script-verify-flag (Invalid Schnorr signature)

Here are steps, psbt1 and psbt2 are different instances:

  1. create input0 and output0 in psbt1
  2. call txscript.TaprootWitnessSignature with txscript.SigHashSingle|txscript.SigHashAnyOneCanPay and use private_key1
  3. updater.Upsbt.Inputs[0].TaprootKeySpendSig = witness from 2
  4. create input0 and output0 in psbt2
  5. append psbt1.input0 as psbt2.input1, append psbt1.output0 as psbt2.output1, also append into UnsignedTx
  6. call txscript.TaprootWitnessSignature with txscript.SigHashDefault and use another private_key
  7. updater.Upsbt.Inputs[0].TaprootKeySpendSig = witness from 6
  8. psbt2.Finalize(0) and psbt2.Finalize(1)
  9. Extract

Then got error.

Here is codes of 5:

psbt2.Inputs = append(psbt2.Inputs, psbt1.Inputs[0])
psbt2.UnsignedTx.TxIn = append(psbt2.UnsignedTx.TxIn, psbt1.UnsignedTx.TxIn[0])
psbt2.UnsignedTx.TxIn[1].SignatureScript = nil
psbt2.Outputs = append(psbt2.Outputs, psbt1.Outputs[0])
psbt2.UnsignedTx.TxOut = append(psbt2.UnsignedTx.TxOut, psbt1.UnsignedTx.TxOut[0])
@Roasbeef
Copy link
Member

Look at the sighashes you're signing (you can insert print statements to see why they differ before the sig msg is hashed). If they're different, then that's why you get an invalid final signature. Also look at the ordering of the keys in the script as well: tapscript doesn't have a normal multi-sig op code, so you either need to use checksigadd or a normal checksig.

@Eoous
Copy link
Contributor Author

Eoous commented Jul 14, 2023

Look at the sighashes you're signing (you can insert print statements to see why they differ before the sig msg is hashed). If they're different, then that's why you get an invalid final signature. Also look at the ordering of the keys in the script as well: tapscript doesn't have a normal multi-sig op code, so you either need to use checksigadd or a normal checksig.

Printed sighashes, they differ. But they generated from different UnsignedTx and OutFetcher, they should be same?

sighashes1:

3f1b201fa7d5793579c08ffb394f76132f2879f9fc4a4057d5263cb0a1abea87
0efd7b36e8b7ac90f08be47960f7a9a626cf4fb17f47afb1c099b7c01b1395ad
f022effa4e64b6a83efb44363f1823e297addbeabe420dfb81f4517e8862905c
b429a131e02198a3c0d252c4e7a1ff05c35fd982180ee5b158b8c5fd477d89e7
161ce34235a6d9212f1aac85096bacb3de11fec85967833b996f48d37614b04f

sighashes2:

f5ba73ad25860cbbbfb25d036a22cb7437900b09baf4d36bee1cab48e189714c
caf0acbb5b59fb577e5ef02af8c429ec2d36330d65d078ee5dce615644aea312
0e325a2c5b8ed4a7eb0e6efdbf97229331acd35235e0fca20a893eded16e42e5
44fb6442528119a34933596c1b5ecb49b0908fb14f7a887dfdc159c28c14a04d
b2df0d6cb74a90c9f73c4bafa597605438385ce86113ee0c6e58b287b5d03546

For result, there're 2 input(first from psbt2, second from psbt1) and 2 output(first from psbt2, second from psbt1).

Alice wants to sell something and receive bitcoin(psbt1, use Single|AnyOneCanPay). Then Bob pays(psbt2, Default) and gets change.
They're both p2tr addresses. I want to figure out how psbt2 can merge pre-signed psbt1.

Perhaps this's not multi-sig? I don't know what to call it.

@Eoous
Copy link
Contributor Author

Eoous commented Jul 14, 2023

All codes:

func TestModularTransfer(t *testing.T) {
	value := 7083
	c, err := rpcclient.New(&rpcclient.ConnConfig{
		Host:         "testnet",
		User:         "user",
		Pass:         "pass",
		HTTPPostMode: true, // Bitcoin core only supports HTTP POST mode
		DisableTLS:   true,
	}, nil)
	if err != nil {
		log.Fatalln(err)
	}

	network := &chaincfg.TestNet3Params
	var sellerPrevOutputFetcher *txscript.MultiPrevOutFetcher
	b := func() string {
		var inputsTxInput []*TxInput
		inputsTxInput = append(inputsTxInput, &TxInput{
			TxId:       "dee67d15626a3f651236da375686ae3b3e14e2b514c5876653c8dbe67b9e2c11",
			VOut:       uint32(0),
			Amount:     int64(435),
			Address:    "tb1pt753u3nvyeny3609suzl0wc8cqdqx978qmp7pcasr6kvr3kluqhqaqyq8n",
			PrivateKey: "priv",
		})

		var outputsTxOutput []*TxOutput
		outputsTxOutput = append(outputsTxOutput, &TxOutput{
			Address: "tb1pt753u3nvyeny3609suzl0wc8cqdqx978qmp7pcasr6kvr3kluqhqaqyq8n",
			Amount:  int64(value - 1000),
		})

		var inputs []*wire.OutPoint
		var nSequences []uint32
		prevOuts := make(map[wire.OutPoint]*wire.TxOut)
		for _, in := range inputsTxInput {
			txHash, err := chainhash.NewHashFromStr(in.TxId)
			if err != nil {
				panic(err)
			}
			prevOut := wire.NewOutPoint(txHash, in.VOut)
			inputs = append(inputs, prevOut)

			prevPkScript, err := AddrToPkScript(in.Address, network)
			if err != nil {
				panic(err)
			}
			witnessUtxo := wire.NewTxOut(in.Amount, prevPkScript)
			prevOuts[*prevOut] = witnessUtxo

			nSequences = append(nSequences, wire.MaxTxInSequenceNum)
		}

		var outputs []*wire.TxOut
		for _, out := range outputsTxOutput {
			pkScript, err := AddrToPkScript(out.Address, network)
			if err != nil {
				panic(err)
			}
			outputs = append(outputs, wire.NewTxOut(out.Amount, pkScript))
		}

		bp, err := psbt.New(inputs, outputs, txVersion, nLockTime, nSequences)
		if err != nil {
			panic(err)
		}

		updater, err := psbt.NewUpdater(bp)
		sellerPrevOutputFetcher = txscript.NewMultiPrevOutFetcher(prevOuts)

		for i, in := range inputsTxInput {
			if err = signInput(updater, i, in, sellerPrevOutputFetcher, txscript.SigHashSingle|txscript.SigHashAnyOneCanPay, network); err != nil {
				panic(err)
			}
		}

		var buf bytes.Buffer
		err = bp.Serialize(&buf)
		if err != nil {
			panic(err)
		}
		buf.Reset()
		b, err := bp.B64Encode()
		fmt.Println("is completed: ", bp.IsComplete())

		return b
	}()

	preSignedpsbt, err := psbt.NewFromRawBytes(strings.NewReader(b), true)
	if err != nil {
		panic(err)
	}

	var inputsTxInput []*TxInput
	inputsTxInput = append(inputsTxInput, &TxInput{
		TxId:       "fa9b34b0b4bfd27910c2e337a31a54a1f8d7e91f0981b8a9cf0b9123753e441c",
		VOut:       uint32(1),
		Amount:     int64(value),
		Address:    "tb1pns05k7ts67gvnxmjvk6n4hkqx4ghpr7h6e7m0q6el8z89lny9tgs6z6dhx",
		PrivateKey: "priv",
	})

	var outputsTxOutput []*TxOutput

	outputsTxOutput = append(outputsTxOutput, &TxOutput{
		Address: "tb1pns05k7ts67gvnxmjvk6n4hkqx4ghpr7h6e7m0q6el8z89lny9tgs6z6dhx",
		Amount:  int64(435),
	})

	var inputs []*wire.OutPoint
	var nSequences []uint32
	prevOuts := make(map[wire.OutPoint]*wire.TxOut)
	for _, in := range inputsTxInput {
		txHash, err := chainhash.NewHashFromStr(in.TxId)
		if err != nil {
			panic(err)
		}
		prevOut := wire.NewOutPoint(txHash, in.VOut)
		inputs = append(inputs, prevOut)

		prevPkScript, err := AddrToPkScript(in.Address, network)
		if err != nil {
			panic(err)
		}
		witnessUtxo := wire.NewTxOut(in.Amount, prevPkScript)
		prevOuts[*prevOut] = witnessUtxo

		nSequences = append(nSequences, wire.MaxTxInSequenceNum)
	}

	var outputs []*wire.TxOut
	for _, out := range outputsTxOutput {
		pkScript, err := AddrToPkScript(out.Address, network)
		if err != nil {
			panic(err)
		}
		outputs = append(outputs, wire.NewTxOut(out.Amount, pkScript))
	}

	bp, err := psbt.New(inputs, outputs, txVersion, nLockTime, nSequences)

	bp.Inputs = append(bp.Inputs, preSignedpsbt.Inputs[0])
	bp.UnsignedTx.TxIn = append(bp.UnsignedTx.TxIn, preSignedpsbt.UnsignedTx.TxIn[0])
	bp.UnsignedTx.TxIn[1].SignatureScript = nil
	bp.Outputs = append(bp.Outputs, preSignedpsbt.Outputs[0])
	bp.UnsignedTx.TxOut = append(bp.UnsignedTx.TxOut, preSignedpsbt.UnsignedTx.TxOut[0])

	updater, err := psbt.NewUpdater(bp)
	if err != nil {
		panic(err)
	}

	prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts)
	prevOutputFetcher.Merge(sellerPrevOutputFetcher)

	for i, in := range inputsTxInput {
		if err = signInput(updater, i, in, prevOutputFetcher, txscript.SigHashDefault, network); err != nil {
			panic(err)
		}

		if err = psbt.Finalize(bp, i); err != nil {
			panic(err)
		}
		if err = psbt.Finalize(bp, i+1); err != nil {
			panic(err)
		}
	}

	buyerSignedTx, err := psbt.Extract(bp)
	if err != nil {
		panic(err)
	}

	var buf bytes.Buffer
	if err = buyerSignedTx.Serialize(&buf); err != nil {
		panic(err)
	}

	fmt.Println("is completed: ", bp.IsComplete())
	fmt.Println(hex.EncodeToString(buf.Bytes()))

	hash, err := c.SendRawTransaction(buyerSignedTx, false)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(hash.String())
}

func signInput(updater *psbt.Updater, i int, in *TxInput, prevOutFetcher *txscript.MultiPrevOutFetcher, hashType txscript.SigHashType, network *chaincfg.Params) error {
	wif, err := btcutil.DecodeWIF(in.PrivateKey)
	if err != nil {
		return err
	}
	privKey := wif.PrivKey

	prevPkScript, err := AddrToPkScript(in.Address, network)
	if err != nil {
		return err
	}

	witnessUtxo := wire.NewTxOut(in.Amount, prevPkScript)
	err = updater.AddInWitnessUtxo(witnessUtxo, i)
	if err != nil {
		return err
	}

	if err = updater.AddInSighashType(hashType, i); err != nil {
		return err
	}

	internalPubKey := schnorr.SerializePubKey(privKey.PubKey())
	updater.Upsbt.Inputs[i].TaprootInternalKey = internalPubKey

	sigHashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevOutFetcher)
	if hashType == txscript.SigHashAll {
		hashType = txscript.SigHashDefault
	}

	witness, err := txscript.TaprootWitnessSignature(updater.Upsbt.UnsignedTx, sigHashes,
		i, in.Amount, prevPkScript, hashType, privKey)
	if err != nil {
		return err
	}

	updater.Upsbt.Inputs[i].TaprootKeySpendSig = witness[0]
	return nil
}

func AddrToPkScript(addr string, network *chaincfg.Params) ([]byte, error) {
	address, err := btcutil.DecodeAddress(addr, network)
	if err != nil {
		return nil, err
	}

	return txscript.PayToAddrScript(address)
}

@guggero
Copy link
Collaborator

guggero commented Jul 14, 2023

Can you print the two extracted raw transactions please? Did you try if things work if you use txscript.SigHashDefault for both PSBTs?

@Eoous
Copy link
Contributor Author

Eoous commented Jul 14, 2023

Can you print the two extracted raw transactions please? Did you try if things work if you use txscript.SigHashDefault for both PSBTs?

Only extracted second psbt:
020000000001021c443e7523910bcfa9b881091fe9d7f8a1541aa337e3c21079d2bfb4b0349bfa0100000000ffffffff112c9e7be6dbc8536687c514b5e2143e3bae865637da3612653f6a62157de6de0000000000ffffffff02b3010000000000002251209c1f4b7970d790c99b7265b53adec03551708fd7d67db78359f9c472fe642ad1d5000000000000002251205fa91e466c266648e9e58705f7bb07c01a0317c706c3e0e3b01eacc1c6dfe02e0140e013c9225b2b406cefc2ed981d88468173189c7b1f9710154e5e5a60fcfd48300cd52bdb24fcf241f0949ed0b4d302a21ca3716a97e5e6709f9a320c51e83752014051fc7ce9402fab8633f200bb37277f8363467dfa57f0006eeae388c6d76e35608745e3948c9ffadd545e99ba63498823c8669f2b1ab62a0ca9c1877680b25b7600000000

Got same error by using txscript.SigHashDefault for both PSBTs.
For first psbt, I need to finalize and extract it?

If i finalize and extract first psbt(use txscript.SigHashDefault) and boardcast it, got error: bad-txns-in-belowout, value in (0.00000435) < value out (0.00006083). That's right.

@guggero
Copy link
Collaborator

guggero commented Jul 14, 2023

Ah, I see what you're attempting to do now. And looking at the TX you sent, it is missing the sighash flag at the end of the signature. So that gets lost somewhere when splicing together the two transactions.

@Eoous
Copy link
Contributor Author

Eoous commented Jul 14, 2023

Ah, I see what you're attempting to do now. And looking at the TX you sent, it is missing the sighash flag at the end of the signature. So that gets lost somewhere when splicing together the two transactions.

I tried to add sighash flag,but not working:

hashtype := txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
updater.AddInSighashType(hashtype, 1)

I changed value of output and tried to add sighash flag, here is new extracted tx:
020000000001021c443e7523910bcfa9b881091fe9d7f8a1541aa337e3c21079d2bfb4b0349bfa0100000000ffffffff112c9e7be6dbc8536687c514b5e2143e3bae865637da3612653f6a62157de6de0000000000ffffffff02b3010000000000002251209c1f4b7970d790c99b7265b53adec03551708fd7d67db78359f9c472fe642ad1c3170000000000002251205fa91e466c266648e9e58705f7bb07c01a0317c706c3e0e3b01eacc1c6dfe02e0140cc92b428180daa19c892becbbd9ae691023f377c17795709daee820485609448e9d22cff2e25409faa6e8afaf6f977ca05d0fe3504ef982660fcd8c2ee2f57730140892b736b69f534d74268f2cf47ce193361e5b3dc5c64ff1a8f8a0d8a5fbb2adafa405f9d3ab2aab5e65d8e3199aec63d39140c4eeb6c212b2764024741061ab300000000

Looks that also there isn't a sighash flag at end of signature.

@guggero
Copy link
Collaborator

guggero commented Jul 14, 2023

Try adding it to the witness manually. I think this might be a bug in the PSBT library that the sighash is ignored when finalizing.

EDIT: This here should take into account the sighash flag:

serializedWitness, err = writeWitness(pInput.TaprootKeySpendSig)

@Eoous
Copy link
Contributor Author

Eoous commented Jul 14, 2023

Try adding it to the witness manually. I think this might be a bug in the PSBT library that the sighash is ignored when finalizing.

EDIT: This here should take into account the sighash flag:

serializedWitness, err = writeWitness(pInput.TaprootKeySpendSig)

It worked, thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants
@Roasbeef @guggero @Eoous and others