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.
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.
- I thought the problem might be in the formats for the server, but also 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 }
- 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:
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=
The big problem is that I don’t understand why sighash is incorrect (and hence Schnorr’s signature), but I don’t think the problem is the formation of the schnorr signature. I think I’m considering something wrong to calculate the sighash OR not considering, although I can’t figure out what exactly, since I don’t get errors at any stage in my code other than checking the BTC CLI or POST to the server