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

Schnorr issue #2279

Closed
MateriaIistGuy opened this issue Nov 28, 2024 · 2 comments
Closed

Schnorr issue #2279

MateriaIistGuy opened this issue Nov 28, 2024 · 2 comments

Comments

@MateriaIistGuy
Copy link

Hi! I want to apologise in advance for perhaps such a simple question for you (judging by the topics discussed here). I'll briefly describe the problem. I receive from API raw PSBT with input indexes to be signed taproot. I have done all possible checks: signhash and taproot calculation, no changes in BASE64 processes, empty symbols, Shnorr validity and that BASE64 is exactly 64 (not 64 + 1), but I get the same error every time. I tried to find some information on BTC stack, but there was very little, and when I asked a question no one understood what the problem was either. I swear I don't understand the problem.

Logic: 1. Receive psbt and decode -> Sign inputs (I don't finalise, the server does) using Default method (ALL the same result)-> encode in Base64 -> return partially signed PSBT to server.

I get the "Invalid Schnorr" error every time by server. You see, if you compare script PSBT and output psbt, they are 99% identical except for Shnorr (field unknown 13). I've attached a screenshot to show the difference.

1. I thought the problem might be in the formats for the server, but Bitcoin cli analyse returns an ‘updater’ status for my script output:

{
    { ‘result’: {
        { ‘inputs’: [
            {
                ‘has_utxo": true,
                ‘is_final": false,
                { ‘next’: { ‘updater’.
            },
            {
                ‘has_utxo": true,
                ‘is_final": false,
                { ‘next’: { ‘updater’.
            }
        ],
        ‘estimated_vsize": 286,
        ‘estimated_feerate": 6.0e-5,
        ‘fee": 1.716e-5,
        ‘next": { ‘updater’.
    },
    ‘error": null,
    ‘id": null
}

2. Although if I sign via Unisat or any other extension wallet - it will return:

{
    ‘result": {
        { ‘inputs’: [
            {
                ‘has_utxo": true,
                ‘is_final": false,
                { ‘next’: { ‘updater’.
            },
            {
                ‘has_utxo": true,
                ‘is_final": false,
                { ‘next’: ‘finalizer’.
            }
        ],
        ‘fee": 1.716e-5,
        ‘next": { ‘updater’
    },
    ‘error": null,
    ‘id": null
}

Code (format via GPT to make more readable, result the same as orginal):

package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"strings"

	"github.com/btcsuite/btcd/btcec/v2/schnorr"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/btcutil/psbt"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
)

func main() {
	wifKeyStr := "WIF"
	psbtBase64 := "cHNidP8BAPwCAAAAAgupxw+/SJhtowTzcr1wx9c/uanFKaJ3yU3J6UKjMTMzlAEAAAD/////Q9g6nnkQbdroR/sr2jeNqZ9nlZbVuqSNdjakxTxLgYADAAAAAP////8ETQEAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOmGDAAAAAAAAIlEgsxHYe7mlcSxOwOnapg4WHRT5UuB+vWF8uMz6Oxu5Bf9DAgAAAAAAABYAFAnkWBtaeW/v/BxPBmkgboAct6nrVkwAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgAAAAAI/AJtZQNzaWdAvHgNmehOZ6jBZiu5ckCncLnVeViAr3PlKIgR2v8NPzhW/OXpu8oaK4b3bgRIL/qrMT5WlpgdybZGXVaAxSJVIwv8Am1lBnNpZ2V4cAhCeTbOW+ZAAAABAStNAQAAAAAAACJRIDCs9kAJfdTiQei7Cf+RSH3Cwr5oxFjNj27APixQD2L4ARcgc+G1jBTUGzdWX447VOZ6zEec2eeffMqg8KTrQ6plriIAAQErrtgAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgEXIK0XtHBW7oxyV677kvzIL56kaRx0i5AADj3yumRxQD17AAAAAAA="
	cleanPSBTBase64 := cleanBase64(psbtBase64)

	psbtBytes, _ := base64.StdEncoding.DecodeString(cleanPSBTBase64)
	packet, _ := psbt.NewFromRawBytes(bytes.NewReader(psbtBytes), false)

	prevOutFetcher := txscript.NewMultiPrevOutFetcher(nil)
	for i, input := range packet.Inputs {
		if input.WitnessUtxo != nil {
			prevOutFetcher.AddPrevOut(packet.UnsignedTx.TxIn[i].PreviousOutPoint, &wire.TxOut{
				Value:    input.WitnessUtxo.Value,
				PkScript: input.WitnessUtxo.PkScript,
			})
		}
	}

	inputToSign := 1
	if len(packet.Inputs[inputToSign].TaprootInternalKey) > 0 {
		sigHashes := txscript.NewTxSigHashes(packet.UnsignedTx, prevOutFetcher)
		pkScript, _ := getInputScript(packet, inputToSign)
		amount, _ := getInputAmount(packet, inputToSign)

		taprootInternalKey := packet.Inputs[inputToSign].TaprootInternalKey
		wifKey, _ := btcutil.DecodeWIF(wifKeyStr)
		publicKeyFromWIF := schnorr.SerializePubKey(wifKey.PrivKey.PubKey())

		if bytes.Equal(taprootInternalKey, publicKeyFromWIF) {
			sighashType := txscript.SigHashDefault
			sighash, _ := txscript.CalcTaprootSignatureHash(sigHashes, sighashType, packet.UnsignedTx, inputToSign, txscript.NewCannedPrevOutputFetcher(pkScript, amount))

			signature, _ := schnorr.Sign(wifKey.PrivKey, sighash)
			serializedSig := signature.Serialize()

			packet.Inputs[inputToSign].TaprootKeySpendSig = serializedSig
		}
	}

	signedPsbtBase64, _ := packet.B64Encode()
	fmt.Printf("Signed PSBT (Base64): %s\n", signedPsbtBase64)
}

func cleanBase64(s string) string {
	return strings.Join(strings.Fields(s), "")
}

func getInputScript(packet *psbt.Packet, idx int) ([]byte, error) {
	input := packet.Inputs[idx]
	if input.WitnessUtxo != nil && len(input.WitnessUtxo.PkScript) > 0 {
		return input.WitnessUtxo.PkScript, nil
	}
	if input.NonWitnessUtxo != nil {
		prevTx := input.NonWitnessUtxo
		outPoint := packet.UnsignedTx.TxIn[idx].PreviousOutPoint
		if int(outPoint.Index) >= len(prevTx.TxOut) {
			return nil, fmt.Errorf("Invalid outpoint index %d for input %d", outPoint.Index, idx)
		}
		return prevTx.TxOut[outPoint.Index].PkScript, nil
	}
	return nil, fmt.Errorf("PkScript not found for input %d", idx)
}

func getInputAmount(packet *psbt.Packet, idx int) (int64, error) {
	input := packet.Inputs[idx]
	if input.WitnessUtxo != nil {
		return input.WitnessUtxo.Value, nil
	}
	if input.NonWitnessUtxo != nil {
		prevTx := input.NonWitnessUtxo
		outPoint := packet.UnsignedTx.TxIn[idx].PreviousOutPoint
		if int(outPoint.Index) >= len(prevTx.TxOut) {
			return 0, fmt.Errorf("Invalid outpoint index %d for input %d", outPoint.Index, idx)
		}
		return prevTx.TxOut[outPoint.Index].Value, nil
	}
	return 0, fmt.Errorf("WitnessUtxo or NonWitnessUtxo not found for input %d", idx)
}


OUTPUT:

cHNidP8BAPwCAAAAAgupxw+/SJhtowTzcr1wx9c/uanFKaJ3yU3J6UKjMTMzlAEAAAD/////Q9g6nnkQbdroR/sr2jeNqZ9nlZbVuqSNdjakxTxLgYADAAAAAP////8ETQEAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOmGDAAAAAAAAIlEgsxHYe7mlcSxOwOnapg4WHRT5UuB+vWF8uMz6Oxu5Bf9DAgAAAAAAABYAFAnkWBtaeW/v/BxPBmkgboAct6nrVkwAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgAAAAAI/AJtZQNzaWdAvHgNmehOZ6jBZiu5ckCncLnVeViAr3PlKIgR2v8NPzhW/OXpu8oaK4b3bgRIL/qrMT5WlpgdybZGXVaAxSJVIwv8Am1lBnNpZ2V4cAhCeTbOW+ZAAAABAStNAQAAAAAAACJRIDCs9kAJfdTiQei7Cf+RSH3Cwr5oxFjNj27APixQD2L4ARcgc+G1jBTUGzdWX447VOZ6zEec2eeffMqg8KTrQ6plriIAAQErrtgAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgEXIK0XtHBW7oxyV677kvzIL56kaRx0i5AADj3yumRxQD17AAAAAAA=
Calculating Taproot address...
Taproot Address: bc1p38pg0767rhujdn569lug887yea73rn7vpwz546y7czzcptql9caq6cafwt
TapKeyBytes: 89c287fb5e1df926ce9a2ff8839fc4cf7d11cfcc0b854ae89ec08580ac1f2e3a
2024/11/27 12:13:42 Taproot Address: bc1p38pg0767rhujdn569lug887yea73rn7vpwz546y7czzcptql9caq6cafwt
Public Key (Hex): ad17b47056ee8c7257aefb92fcc82f9ea4691c748b90000e3df2ba6471403d7b
Decoding PSBT...
PSBT successfully decoded.
Parsing PSBT...
PSBT successfully parsed.
Creating MultiPrevOutFetcher...
Adding previous output for input 0
Adding previous output for input 1
MultiPrevOutFetcher successfully created.
Signing input 1...
Input uses Taproot.
Creating TxSigHashes...
TxSigHashes successfully created.
PkScript: 512089c287fb5e1df926ce9a2ff8839fc4cf7d11cfcc0b854ae89ec08580ac1f2e3a
Amount: 55470
TaprootInternalKey of input 1: ad17b47056ee8c7257aefb92fcc82f9ea4691c748b90000e3df2ba6471403d7b
Public key from WIF: ad17b47056ee8c7257aefb92fcc82f9ea4691c748b90000e3df2ba6471403d7b
The signing key matches the TaprootInternalKey of the input.
Calculating sighash...
Sighash: 92aec029f002090d7621fffce4ebac7ff333d971e832db3980bb93112cb851ce
Creating signature...
Signature: 98dcfc29a85bc89ed35a86fa8073d5708ea93e095f4171b65c72ed12227d537a849b539ef6f9b845aeef61ff0bd4e94b664981e14a29e2443ddca98ecb9360f3
Signature for input 1 added.
Serializing signed PSBT...
Signed PSBT (Base64): cHNidP8BAPwCAAAAAgupxw+/SJhtowTzcr1wx9c/uanFKaJ3yU3J6UKjMTMzlAEAAAD/////Q9g6nnkQbdroR/sr2jeNqZ9nlZbVuqSNdjakxTxLgYADAAAAAP////8ETQEAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOmGDAAAAAAAAIlEgsxHYe7mlcSxOwOnapg4WHRT5UuB+vWF8uMz6Oxu5Bf9DAgAAAAAAABYAFAnkWBtaeW/v/BxPBmkgboAct6nrVkwAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgAAAAAI/AJtZQNzaWdAvHgNmehOZ6jBZiu5ckCncLnVeViAr3PlKIgR2v8NPzhW/OXpu8oaK4b3bgRIL/qrMT5WlpgdybZGXVaAxSJVIwv8Am1lBnNpZ2V4cAhCeTbOW+ZAAAABAStNAQAAAAAAACJRIDCs9kAJfdTiQei7Cf+RSH3Cwr5oxFjNj27APixQD2L4ARcgc+G1jBTUGzdWX447VOZ6zEec2eeffMqg8KTrQ6plriIAAQErrtgAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgETQJjc/CmoW8ie01qG+oBz1XCOqT4JX0Fxtlxy7RIifVN6hJtTnvb5uEWu72H/C9TpS2ZJgeFKKeJEPdypjsuTYPMBFyCtF7RwVu6Mcleu+5L8yC+epGkcdIuQAA498rpkcUA9ewAAAAAA


PSBT MY Script output:
cHNidP8BAPwCAAAAAgupxw+/SJhtowTzcr1wx9c/uanFKaJ3yU3J6UKjMTMzlAEAAAD/////Q9g6nnkQbdroR/sr2jeNqZ9nlZbVuqSNdjakxTxLgYADAAAAAP////8ETQEAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOmGDAAAAAAAAIlEgsxHYe7mlcSxOwOnapg4WHRT5UuB+vWF8uMz6Oxu5Bf9DAgAAAAAAABYAFAnkWBtaeW/v/BxPBmkgboAct6nrVkwAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgAAAAAI/AJtZQNzaWdAvHgNmehOZ6jBZiu5ckCncLnVeViAr3PlKIgR2v8NPzhW/OXpu8oaK4b3bgRIL/qrMT5WlpgdybZGXVaAxSJVIwv8Am1lBnNpZ2V4cAhCeTbOW+ZAAAABAStNAQAAAAAAACJRIDCs9kAJfdTiQei7Cf+RSH3Cwr5oxFjNj27APixQD2L4ARcgc+G1jBTUGzdWX447VOZ6zEec2eeffMqg8KTrQ6plriIAAQErrtgAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgETQJjc/CmoW8ie01qG+oBz1XCOqT4JX0Fxtlxy7RIifVN6hJtTnvb5uEWu72H/C9TpS2ZJgeFKKeJEPdypjsuTYPMBFyCtF7RwVu6Mcleu+5L8yC+epGkcdIuQAA498rpkcUA9ewAAAAAA

Wallet extension PSBT:
cHNidP8BAPwCAAAAAgupxw+/SJhtowTzcr1wx9c/uanFKaJ3yU3J6UKjMTMzlAEAAAD/////Q9g6nnkQbdroR/sr2jeNqZ9nlZbVuqSNdjakxTxLgYADAAAAAP////8ETQEAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOmGDAAAAAAAAIlEgsxHYe7mlcSxOwOnapg4WHRT5UuB+vWF8uMz6Oxu5Bf9DAgAAAAAAABYAFAnkWBtaeW/v/BxPBmkgboAct6nrVkwAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgAAAAAI/AJtZQNzaWdAvHgNmehOZ6jBZiu5ckCncLnVeViAr3PlKIgR2v8NPzhW/OXpu8oaK4b3bgRIL/qrMT5WlpgdybZGXVaAxSJVIwv8Am1lBnNpZ2V4cAhCeTbOW+ZAAAABAStNAQAAAAAAACJRIDCs9kAJfdTiQei7Cf+RSH3Cwr5oxFjNj27APixQD2L4ARcgc+G1jBTUGzdWX447VOZ6zEec2eeffMqg8KTrQ6plriIAAQErrtgAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgETQF/kABzHRRZdy9+33rcvJZxMBYhwywu6TY0f+XOEbwBdvcjHcy2tYjR2VM2YbAFCaS9xtHx+SgfAS6RmvTh4xh0BFyCtF7RwVu6Mcleu+5L8yC+epGkcdIuQAA498rpkcUA9ewAAAAAA

Raw PSBT (input psbt fot scirpt and extension):
cHNidP8BAPwCAAAAAgupxw+/SJhtowTzcr1wx9c/uanFKaJ3yU3J6UKjMTMzlAEAAAD/////Q9g6nnkQbdroR/sr2jeNqZ9nlZbVuqSNdjakxTxLgYADAAAAAP////8ETQEAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOmGDAAAAAAAAIlEgsxHYe7mlcSxOwOnapg4WHRT5UuB+vWF8uMz6Oxu5Bf9DAgAAAAAAABYAFAnkWBtaeW/v/BxPBmkgboAct6nrVkwAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgAAAAAI/AJtZQNzaWdAvHgNmehOZ6jBZiu5ckCncLnVeViAr3PlKIgR2v8NPzhW/OXpu8oaK4b3bgRIL/qrMT5WlpgdybZGXVaAxSJVIwv8Am1lBnNpZ2V4cAhCeTbOW+ZAAAABAStNAQAAAAAAACJRIDCs9kAJfdTiQei7Cf+RSH3Cwr5oxFjNj27APixQD2L4ARcgc+G1jBTUGzdWX447VOZ6zEec2eeffMqg8KTrQ6plriIAAQErrtgAAAAAAAAiUSCJwof7Xh35Js6aL/iDn8TPfRHPzAuFSuiewIWArB8uOgEXIK0XtHBW7oxyV677kvzIL56kaRx0i5AADj3yumRxQD17AAAAAAA=

{2F18EB68-B3FA-4F50-B815-90F4F8C4360B}

@guggero
Copy link
Collaborator

guggero commented Nov 28, 2024

Things look correct, as far as I can tell.
Perhaps an issue with the pk script and amount.
Why don't you just use sighash, _ := txscript.CalcTaprootSignatureHash(sigHashes, sighashType, packet.UnsignedTx, inputToSign, prevOutFetcher)?
Then you don't need getInputAmount and getInputScript.

@MateriaIistGuy
Copy link
Author

Yes, thank you. Already added everything I could as my head was spinning. I made debug logs for PSBT formation, everything seems to be correct, but same invalid Shnorr and:

"has_utxo": true,
"is_final": false,
"next": "updater" (not expected finilizer status)

I tried to understand why expansion wallets do it the way they should, but I didn't find anything. I think I saw only Unisat wallet (I may be wrong), but I didn't understand how it does it with the same execution steps

@MateriaIistGuy MateriaIistGuy changed the title Shnorr issue Schnorr issue Nov 29, 2024
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

2 participants