btcsuite / btcd

An alternative full node bitcoin implementation written in Go (golang)
https://github.com/btcsuite/btcd/blob/master/README.md
ISC License
6.2k stars 2.35k forks source link

How to sign form and sign P2SH transaction #1878

Closed roman-mc closed 2 years ago

roman-mc commented 2 years ago

I have a trouble with forming and signing P2SH transaction. I have no trouble with signing P2PKH, only with P2SH

After evaluating the script (vm.Execute) I get error "false stack entry at end of script execution"

So the code is:

import (
    "bytes"
    "context"
    "encoding/hex"
    "fmt"
    "github.com/Laconty/go-electrum/electrum"
    "github.com/btcsuite/btcd/btcutil"
    "github.com/btcsuite/btcd/chaincfg"
    "github.com/btcsuite/btcd/chaincfg/chainhash"
    "github.com/btcsuite/btcd/txscript"
    "github.com/btcsuite/btcd/wire"
    "log"
    "time"
)

// P2WSH, and private key
// 2NBg4RNi1P2jyRb7wGxL3juSHrXjZfMwejB  p2wpkh-p2sh:cPU4pnqJqR38ebnevKvHSz8LidB63s7QeQmkdZGsmXM5SmiZaJnH

func CreateTransactionV2(privKey string, destination string, amount int64) (string, error) {
    privKey, destination, amount =
        "cPU4pnqJqR38ebnevKvHSz8LidB63s7QeQmkdZGsmXM5SmiZaJnH",
        "2NBg4RNi1P2jyRb7wGxL3juSHrXjZfMwejB",
        68500
    // prev utx
    txid, balance, pkScript, err := "445cbd3fb1f66467a2404b65428c0e7dfe015a560c2d3552fd7c2743648d39a2", int64(70000), "a914ca24ad3bd254295840b27f3745f42461fb805da687", error(nil)
    //sourcePkgScript :=
    //txid, balance, pkScript, err := GetUTXO(addrPubKey.EncodeAddress())
    if err != nil {
        return "", err
    }

    /*
     * 1 or unit-amount in Bitcoin is equal to 1 satoshi and 1 Bitcoin = 100_000_000 satoshi
     */

    // checking for sufficiency of account
    if balance < amount {
        return "", fmt.Errorf("the balance of the account is not sufficient")
    }

    // extracting destination address as []byte from function argument (destination string)
    destinationAddr, err := btcutil.DecodeAddress(destination, &chaincfg.TestNet3Params)
    if err != nil {
        return "", err
    }

    // locking script
    destinationAddrByte, err := txscript.PayToAddrScript(destinationAddr)
    if err != nil {
        return "", err
    }

    //p2shAddressObj := btcutil.NewAddressScriptHashFromHash()

    // creating a new bitcoin transaction, different sections of the tx, including
    // input list (contain UTXOs) and outputlist (contain destination address and usually our address)
    // in next steps, sections will be field and pass to sign
    redeemTx := wire.NewMsgTx(wire.TxVersion)

    utxoHash, err := chainhash.NewHashFromStr(txid)
    if err != nil {
        return "", err
    }
    // the second argument is vout or Tx-index, which is the index
    // of spending UTXO in the transaction that Txid referred to
    outPoint := wire.NewOutPoint(utxoHash, 0)

    // making the input, and adding it to transaction
    txIn := wire.NewTxIn(outPoint, nil, nil)
    redeemTx.AddTxIn(txIn)

    // adding the destination address and the amount to
    // the transaction as output
    redeemTxOut := wire.NewTxOut(amount, destinationAddrByte)
    redeemTx.AddTxOut(redeemTxOut)

    // now sign the transaction
    finalRawTx, redeemTx, err := SignTx(privKey, pkScript, redeemTx)

    return finalRawTx, nil
}

func SignTx(privKey string, pkScript string, redeemTx *wire.MsgTx) (string, *wire.MsgTx, error) {
    wif, err := btcutil.DecodeWIF(privKey)
    if err != nil {
        return "", nil, err
    }

    sourcePKScript, err := hex.DecodeString(pkScript)
    if err != nil {
        return "", nil, nil
    }

    // since there is only one input in our transaction
    // we use 0 as second argument, if the transaction
    // has more args, should pass related index
    signature, err := txscript.SignatureScript(redeemTx, 0, sourcePKScript, txscript.SigHashAll, wif.PrivKey, true)
    if err != nil {
        return "", nil, nil
    }

    // since there is only one input, and want to add
    // signature to it use 0 as index
    redeemTx.TxIn[0].SignatureScript = signature

    if err != nil {
        log.Fatal(err)
    }

    var serializedSignedTx bytes.Buffer
    err = redeemTx.Serialize(&serializedSignedTx)
    if err != nil {
        return "", nil, err
    }
    hexSignedTx := hex.EncodeToString(serializedSignedTx.Bytes())
    vm, err := txscript.NewEngine(sourcePKScript, redeemTx, 0, txscript.StandardVerifyFlags, nil, nil, 68500, nil)
    if err != nil {
        log.Fatal(err)
    }

    err = vm.Execute()
    if err != nil {
        log.Fatal(err)
    }

    return hexSignedTx, redeemTx, nil
}
Roasbeef commented 2 years ago

Check out how P2SH works in detail: https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki

(btw you should likely just use p2wsh or p2tr since those are the more modern formats)

Issues with your script at a glance:

roman-mc commented 2 years ago

Thank you @Roasbeef for informative answer

btw you should likely just use p2wsh

Probably you are right, are there examples of p2wsh? 🙂 Or the link you attached is for p2wsh too?

Roasbeef commented 2 years ago

Here's an example: https://github.com/lightningnetwork/lnd/blob/master/input/script_utils.go#L325-L351

In this case, this method would be used for signing: https://pkg.go.dev/github.com/btcsuite/btcd/txscript#RawTxInWitnessSignature